]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - cmd/safcm-remote/sync/files.go
safcm: add experimental support to sync from Windows hosts
[safcm/safcm.git] / cmd / safcm-remote / sync / files.go
index 12508ce08c2f7e7d2c17b74dca980285ec01f334..e0a2221009deda9824aa38419fec6ffc1decc0f0 100644 (file)
@@ -15,6 +15,8 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+// +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("<binary content>\n")
+               oldData = []byte(fmt.Sprintf("<binary content, %d bytes>\n",
+                       len(oldData)))
        }
        if newBin {
-               newData = []byte("<binary content>\n")
+               newData = []byte(fmt.Sprintf("<binary content, %d bytes>\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