J.Doe
J.Doe

Reputation: 413

Golang tarring directory

I am having trouble figuring out how to write a directory into tarball's headerinfo. For example, I have this directory structure:

test [dir]
-- test1.txt
    -- subDirA [sub dir]
         -- test2.txt

If I tar up test using my Go tar executable, it will untar and have the original structure. If I do a tar -tvf on the tarball, the listing will be:

test/test1.txt 
test/subDirA/test2.txt

However if I manually do a tar -cvf of test and then do a tar -tvf, the listing will be:

test/
test/test1.txt
test/subDirA/
test/subDirA/test2.txt

This is what I want my Go tarring program to do as well. In my Go program, I tried to add a new method writeDirToTar to deal with adding directories into the tarWriter. I call either writeFileToTar or writeDirToTar from a fileWalk function. I am getting a "test/subDirA is a directory" error when I call io.Copy(tarWriter, file) in writeDirToTar, which kind of makes sense. Is there a workaround for this?

func writeToTar(fileDir string,
        sourceBase string,
        tarWriter *tar.Writer,
        f os.FileInfo) error {

        file, err := os.Open(fileDir)
        if err != nil {
                log.Print("error opening file")
                return err
        }
        defer file.Close()

        // relative paths are used to preserve the directory paths in each file path
        relativePath, err := filepath.Rel(sourceBase, fileDir)

        tarheader := new(tar.Header)
        tarheader.Name = relativePath
        tarheader.Size = f.Size()
        tarheader.Mode = int64(f.Mode())
        tarheader.ModTime = f.ModTime()

        err = tarWriter.WriteHeader(tarheader)
        if err != nil {
                log.Print("error writing tarHeader")
                return err
        }
        _, err = io.Copy(tarWriter, file)
        if err != nil {
                log.Print("error writing to tarWriter")
                return err
        }
        return nil
}

func writeDirToTar(fileDir string,
        sourceBase string,
        tarWriter *tar.Writer,
        f os.FileInfo) error {

        file, err := os.Open(fileDir)
        if err != nil {
                log.Print("error opening file")
                return err
        }
        defer file.Close()

        // relative paths are used to preserve the directory paths in each file path
        relativePath, err := filepath.Rel(sourceBase, fileDir)

        if tarHeader, err := tar.FileInfoHeader(f, relativePath); err != nil {
                log.Print("error writing tarHeader")
                return err
        }

        tarHeader.Name = relativePath
        tarheader.Mode = int64(f.Mode())
        tarheader.ModTime = f.ModTime()

        _, err = io.Copy(tarWriter, file)
        if err != nil {
                log.Print("error writing to tarWriter")
                return err
        }
        return nil

}

Upvotes: 3

Views: 3765

Answers (1)

Mr_Pink
Mr_Pink

Reputation: 109401

You're missing the Typeflag field in the tar.Header.

For the directory you'll want at least:

tarheader := &tar.Header{
    Name:     relativePath,
    Mode:     int64(f.Mode()),
    ModTime:  f.ModTime(),
    Typeflag: tar.TypeDir,
}

You don't want a size here, nor do you want to copy the directory data, since the internal directory data structures mean nothing to tar.

You may also want to add a / to the filename to visually indicate it's a directory and match the POSIX tar behavior.

Alternatively you could just use the tar.FileInfoHeader function just like you did with the files to create the proper header structure.

Upvotes: 2

Related Questions