Skip to content

Instantly share code, notes, and snippets.

@jaybill
Created June 5, 2012 17:46
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jaybill/2876519 to your computer and use it in GitHub Desktop.
Save jaybill/2876519 to your computer and use it in GitHub Desktop.
How to copy a directory tree (preserving permissions) in Go.
package main
import "fmt"
import "os"
import "io"
import "io/ioutil"
import "log"
// Copies file source to destination dest.
func CopyFile(source string, dest string) (err error) {
sf, err := os.Open(source)
if err != nil {
return err
}
defer sf.Close()
df, err := os.Create(dest)
if err != nil {
return err
}
defer df.Close()
_, err = io.Copy(df, sf)
if err == nil {
si, err := os.Stat(source)
if err != nil {
err = os.Chmod(dest, si.Mode())
}
}
return
}
// Recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
func CopyDir(source string, dest string) (err error) {
// get properties of source dir
fi, err := os.Stat(source)
if err != nil {
return err
}
if !fi.IsDir() {
return &CustomError{"Source is not a directory"}
}
// ensure dest dir does not already exist
_, err = os.Open(dest)
if !os.IsNotExist(err) {
return &CustomError{"Destination already exists"}
}
// create dest dir
err = os.MkdirAll(dest, fi.Mode())
if err != nil {
return err
}
entries, err := ioutil.ReadDir(source)
for _, entry := range entries {
sfp := source + "/" + entry.Name()
dfp := dest + "/" + entry.Name()
if entry.IsDir() {
err = CopyDir(sfp, dfp)
if err != nil {
log.Println(err)
}
} else {
// perform copy
err = CopyFile(sfp, dfp)
if err != nil {
log.Println(err)
}
}
}
return
}
// A struct for returning custom error messages
type CustomError struct {
What string
}
// Returns the error message defined in What as a string
func (e *CustomError) Error() string {
return e.What
}
func main() {
err = CopyDir("/home/jaybill/data", "/home/jaybill/backup")
if err != nil {
log.Fatal(err)
} else {
log.Print("Files copied.")
}
}
@r0l1
Copy link

r0l1 commented Sep 16, 2016

There is an error:

    if err == nil {
        si, err := os.Stat(source)
        if err != nil {
            err = os.Chmod(dest, si.Mode())
        }
    }

should be

    if err == nil {
        si, err := os.Stat(source)
        if err == nil {
            err = os.Chmod(dest, si.Mode())
        }
    }

However still one error is not handled right...

@r0l1
Copy link

r0l1 commented Sep 16, 2016

And you didn't handle the error case here:

    entries, err := ioutil.ReadDir(source)

    for _, entry := range entries {

@r0l1
Copy link

r0l1 commented Sep 16, 2016

And don't use log.Println! Return the error instead!

sfp := source + "/" + entry.Name()
dfp := dest + "/" + entry.Name()
if entry.IsDir() {
    err = CopyDir(sfp, dfp)
    if err != nil {
        log.Println(err)
    }
} else {
    // perform copy         
    err = CopyFile(sfp, dfp)
    if err != nil {
        log.Println(err)
    }
}

@r0l1
Copy link

r0l1 commented Sep 16, 2016

@lizrice
Copy link

lizrice commented Oct 6, 2017

This seems useful, but the link to the @r0l1 clean implementation seems to be broken unfortunately, and I think it should be here: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment