X-Git-Url: https://ruderich.org/simon/gitweb/?p=safcm%2Fsafcm.git;a=blobdiff_plain;f=cmd%2Fsafcm-remote%2Fsync%2Ffiles.go;h=e0a2221009deda9824aa38419fec6ffc1decc0f0;hp=12508ce08c2f7e7d2c17b74dca980285ec01f334;hb=6a40d84afc959f404f243b1c00ab95dc9dd9c721;hpb=713cde4bd701dd53ed46d01f43c9e9e7b82dc514 diff --git a/cmd/safcm-remote/sync/files.go b/cmd/safcm-remote/sync/files.go index 12508ce..e0a2221 100644 --- a/cmd/safcm-remote/sync/files.go +++ b/cmd/safcm-remote/sync/files.go @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// +build !windows + package sync import ( @@ -113,7 +115,7 @@ reopen: oldFh, err := OpenFileNoFollow(file.Path) if err != nil { err := err.(*fs.PathError) - if err.Err == syscall.ELOOP { + if err.Err == syscall.ELOOP || err.Err == syscall.EMLINK { // Check if ELOOP was caused not by O_NOFOLLOW but by // too many nested symlinks before the final path // component. @@ -149,6 +151,15 @@ reopen: if !change.Created { // Compare permissions change.Old.Mode = oldStat.Mode() + if change.Old.Mode.Type() == fs.ModeSymlink { + // Some BSD systems permit changing permissions of + // symlinks but ignore them on traversal. To keep it + // simple we don't support that and always use 0777 + // for symlink permissions (the value on GNU/Linux). + // + // TODO: Add proper support for symlinks on BSD + change.Old.Mode |= 0777 + } if change.Old.Mode != file.Mode { if change.Old.Mode.Type() != file.Mode.Type() { changeType = true @@ -258,7 +269,7 @@ reopen: } } - // Directory: create new directory (also type change to directory) + // Directory: create new directory, also type change to directory if file.Mode.IsDir() && (change.Created || changeType) { debugf("creating directory") err := os.Mkdir(file.Path, 0700) @@ -328,50 +339,18 @@ reopen: } dir := filepath.Dir(file.Path) - base := filepath.Base(file.Path) + // Create hidden file which should be ignored by most other tools and + // thus not affect anything during creation + base := "." + filepath.Base(file.Path) var tmpPath string switch file.Mode.Type() { case 0: // regular file debugf("creating temporary file %q", - filepath.Join(dir, "."+base+"*")) - // Create hidden file which should be ignored by most other - // tools and thus not affect anything during creation - newFh, err := os.CreateTemp(dir, "."+base) - if err != nil { - return err - } - tmpPath = newFh.Name() - - _, err = newFh.Write(file.Data) - if err != nil { - newFh.Close() - os.Remove(tmpPath) - return err - } - // CreateTemp() creates the file with 0600 - err = newFh.Chown(file.Uid, file.Gid) - if err != nil { - newFh.Close() - os.Remove(tmpPath) - return err - } - err = newFh.Chmod(file.Mode) + filepath.Join(dir, base+"*")) + tmpPath, err = WriteTemp(dir, base, file.Data, + file.Uid, file.Gid, file.Mode) if err != nil { - newFh.Close() - os.Remove(tmpPath) - return err - } - err = newFh.Sync() - if err != nil { - newFh.Close() - os.Remove(tmpPath) - return err - } - err = newFh.Close() - if err != nil { - newFh.Close() - os.Remove(tmpPath) return err } @@ -381,7 +360,7 @@ reopen: // Similar to os.CreateTemp() but for symlinks which we cannot // open as file tmpPath = filepath.Join(dir, - "."+base+strconv.Itoa(rand.Int())) + base+strconv.Itoa(rand.Int())) debugf("creating temporary symlink %q", tmpPath) err := os.Symlink(string(file.Data), tmpPath) if err != nil { @@ -396,7 +375,7 @@ reopen: os.Remove(tmpPath) return err } - // Permissions are irrelevant for symlinks + // Permissions are irrelevant for symlinks (on most systems) default: panic(fmt.Sprintf("invalid file type %s", file.Mode)) @@ -408,7 +387,7 @@ reopen: os.Remove(tmpPath) return err } - err = syncPath(dir) + err = SyncPath(dir) if err != nil { return err } @@ -475,13 +454,16 @@ func diffData(oldData []byte, newData []byte) (string, error) { oldBin := !strings.HasPrefix(http.DetectContentType(oldData), "text/") newBin := !strings.HasPrefix(http.DetectContentType(newData), "text/") if oldBin && newBin { - return "Binary files differ, cannot show diff", nil + return fmt.Sprintf("Binary files differ (%d -> %d bytes), "+ + "cannot show diff", len(oldData), len(newData)), nil } if oldBin { - oldData = []byte("\n") + oldData = []byte(fmt.Sprintf("\n", + len(oldData))) } if newBin { - newData = []byte("\n") + newData = []byte(fmt.Sprintf("\n", + len(newData))) } // TODO: difflib shows empty context lines at the end of the file @@ -505,7 +487,51 @@ func OpenFileNoFollow(path string) (*os.File, error) { os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0) } -// syncPath syncs path, which should be a directory. To guarantee durability +func WriteTemp(dir, base string, data []byte, uid, gid int, mode fs.FileMode) ( + string, error) { + + fh, err := os.CreateTemp(dir, base) + if err != nil { + return "", err + } + tmpPath := fh.Name() + + _, err = fh.Write(data) + if err != nil { + fh.Close() + os.Remove(tmpPath) + return "", err + } + // CreateTemp() creates the file with 0600 + err = fh.Chown(uid, gid) + if err != nil { + fh.Close() + os.Remove(tmpPath) + return "", err + } + err = fh.Chmod(mode) + if err != nil { + fh.Close() + os.Remove(tmpPath) + return "", err + } + err = fh.Sync() + if err != nil { + fh.Close() + os.Remove(tmpPath) + return "", err + } + err = fh.Close() + if err != nil { + fh.Close() + os.Remove(tmpPath) + return "", err + } + + return tmpPath, nil +} + +// SyncPath syncs path, which should be a directory. To guarantee durability // it must be called on a parent directory after adding, renaming or removing // files therein. // @@ -513,7 +539,7 @@ func OpenFileNoFollow(path string) (*os.File, error) { // fsync: "Calling fsync() does not necessarily ensure that the entry in the // directory containing the file has also reached disk. For that an explicit // fsync() on a file descriptor for the directory is also needed." -func syncPath(path string) error { +func SyncPath(path string) error { x, err := os.Open(path) if err != nil { return err