X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;ds=inline;f=cmd%2Fsafcm-remote%2Fsync%2Ffiles.go;h=e0a2221009deda9824aa38419fec6ffc1decc0f0;hb=8a3f6af248e28ea7efc1bf89751a597d28834942;hp=06bc4066643b1394b286363e2bd7665a136ed722;hpb=f2f2bc47e8729548f3c10117f7f008b547c4afc5;p=safcm%2Fsafcm.git
diff --git a/cmd/safcm-remote/sync/files.go b/cmd/safcm-remote/sync/files.go
index 06bc406..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 (
@@ -110,13 +112,10 @@ func (s *Sync) syncFile(file *safcm.File, changed *bool) error {
var oldStat fs.FileInfo
reopen:
- oldFh, err := os.OpenFile(file.Path,
- // O_NOFOLLOW prevents symlink attacks
- // O_NONBLOCK is necessary to prevent blocking on FIFOs
- os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
+ 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.
@@ -152,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
@@ -261,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)
@@ -273,8 +281,7 @@ reopen:
// a symlink at this point. There's no lchmod so open the
// directory.
debugf("chmodding %s", file.Mode)
- dh, err := os.OpenFile(file.Path,
- os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
+ dh, err := OpenFileNoFollow(file.Path)
if err != nil {
return err
}
@@ -332,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)
- if err != nil {
- newFh.Close()
- os.Remove(tmpPath)
- return err
- }
- err = newFh.Sync()
+ 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.Close()
- if err != nil {
- newFh.Close()
- os.Remove(tmpPath)
return err
}
@@ -385,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 {
@@ -400,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))
@@ -412,7 +387,7 @@ reopen:
os.Remove(tmpPath)
return err
}
- err = syncPath(dir)
+ err = SyncPath(dir)
if err != nil {
return err
}
@@ -479,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
@@ -502,7 +480,58 @@ func diffData(oldData []byte, newData []byte) (string, error) {
return result, nil
}
-// syncPath syncs path, which should be a directory. To guarantee durability
+func OpenFileNoFollow(path string) (*os.File, error) {
+ return os.OpenFile(path,
+ // O_NOFOLLOW prevents symlink attacks
+ // O_NONBLOCK is necessary to prevent blocking on FIFOs
+ os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
+}
+
+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.
//
@@ -510,7 +539,7 @@ func diffData(oldData []byte, newData []byte) (string, 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