Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to write a directory [not just the files in it ] to a tar.gz file in golang

Tags:

gzip

go

tar

I want to write a tar_gz tool in Go. The input is just like linux command:

$tar czvf targetFileName inputDirectoryPath

Suppose I have an inputDirectory structured as below:

test [dir]
-- 0.txt
    -- 1 [sub dir]
         -- 1.txt

For Example: use command:

$tar czvf test.tar.gz test/

we can tar and gzip the whole test directory.


My Problem is I can write a tar and gz route to recursively iterate all the file in test directory and write the file to test.tar.gz file. But I don't know how to write a directory to the test.tar.gz. After running my program, the structure in test.tar.gz file is:

0.txt
1.txt

Can anyone tell me how to write the directory recursively to the output tar.gz file. Thanks a lot.

    package main

    import (
      "fmt"
      "os"
      "io"
      "log"
      "strings"
      "archive/tar"
      "compress/gzip"
    )

    func handleError( _e error ) {
      if _e != nil {
        log.Fatal( _e )
      }
    }

    func TarGzWrite( _path string, tw *tar.Writer, fi os.FileInfo ) {
      fr, err := os.Open( _path )
      handleError( err )
      defer fr.Close()

      h := new( tar.Header )
      h.Name = fi.Name()
      h.Size = fi.Size()
      h.Mode = int64( fi.Mode() )
      h.ModTime = fi.ModTime()

      err = tw.WriteHeader( h )
      handleError( err )

      _, err = io.Copy( tw, fr )
      handleError( err )
    }

    func IterDirectory( dirPath string, tw *tar.Writer ) {
      dir, err := os.Open( dirPath )
      handleError( err )
      defer dir.Close()
      fis, err := dir.Readdir( 0 )
      handleError( err )
      for _, fi := range fis {
        curPath := dirPath + "/" + fi.Name()
        if fi.IsDir() {
          //TarGzWrite( curPath, tw, fi )
          IterDirectory( curPath, tw )
        } else {
          fmt.Printf( "adding... %s\n", curPath )
          TarGzWrite( curPath, tw, fi )
        }
      }
    }

    func TarGz( outFilePath string, inPath string ) {
      // file write
      fw, err := os.Create( outFilePath )
      handleError( err )
      defer fw.Close()

      // gzip write
      gw := gzip.NewWriter( fw )
      defer gw.Close()

      // tar write
      tw := tar.NewWriter( gw )
      defer tw.Close()

      IterDirectory( inPath, tw )

      fmt.Println( "tar.gz ok" )
    }

    func main() {
      targetFilePath := "test.tar.gz"
      inputDirPath := "test/"
      TarGz( targetFilePath, strings.TrimRight( inputDirPath, "/" ) )
      fmt.Println( "Hello, World" )
    }
like image 386
MadCrazy Avatar asked Nov 28 '12 17:11

MadCrazy


2 Answers

An alternative is to use the built in filepath.Walk function

// root_directory has been set further up

walkFn := func(path string, info os.FileInfo, err error) error {
    if info.Mode().IsDir() {
        return nil
    }
    // Because of scoping we can reference the external root_directory variable
    new_path := path[len(root_directory):]
    if len(new_path) == 0 {
        return nil
    }
    fr, err := os.Open(path)
    if err != nil {
        return err
    }
    defer fr.Close()

    if h, err := tar.FileInfoHeader(info, new_path); err != nil {
        log.Fatalln(err)
    } else {
        h.Name = new_path
        if err = tw.WriteHeader(h); err != nil {
            log.Fatalln(err)
        }
    }
    if length, err := io.Copy( tw, fr ); err != nil {
        log.Fatalln(err)
    } else {
        fmt.Println(length)
    }
    return nil
}

if err = filepath.Walk(root_directory, walkFn); err != nil {
    return err
}
like image 90
Tyndyll Avatar answered Oct 19 '22 04:10

Tyndyll


You're only adding the filename to the tar, not the entire path. You need to keep the whole path for Tar to be able to understand directories. You just need to change one line:

h.Name = fi.Name()

Should be:

h.Name = _path

On Linux, the output of tar -tvf test.tar.gz:

-rw-rw-r-- 0/0               0 2012-11-28 11:17 test/0.txt
-rw-rw-r-- 0/0               0 2012-11-28 11:17 test/sub/1.txt
like image 40
beatgammit Avatar answered Oct 19 '22 04:10

beatgammit