]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - remote/sync/files.go
Use SPDX license identifiers
[safcm/safcm.git] / remote / sync / files.go
index 9803e936093d1c3edf59c3881ac2966a041ab3c2..9b72bbcacc9644b0eaca4990ab08e12213385e5e 100644 (file)
@@ -1,19 +1,7 @@
 // MsgSyncReq: copy files to the remote host
 
-// Copyright (C) 2021  Simon Ruderich
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright (C) 2021-2024  Simon Ruderich
 
 //go:build !windows
 // +build !windows
@@ -49,7 +37,7 @@ import (
 // openReadonlyFlags are flags for open* syscalls to safely read a file or
 // directory.
 //
-// O_NOFOLLOW prevents symlink attacks
+// O_NOFOLLOW prevents symlink attacks in the last path component
 // O_NONBLOCK is necessary to prevent blocking on FIFOs
 const openReadonlyFlags = unix.O_RDONLY | unix.O_NOFOLLOW | unix.O_NONBLOCK
 
@@ -83,13 +71,13 @@ func (s *Sync) syncFiles() error {
 
 func (s *Sync) syncFile(file *safcm.File, changed *bool) error {
        // The general strategy is "update by rename": If any property of a
-       // file changes it will be written to a temporary file and then
-       // renamed "over" the original file. This is simple and prevents race
-       // conditions where the file is partially readable while changes to
-       // permissions or owner/group are applied. However, this strategy does
-       // not work for directories which must be removed first (was
-       // directory), must remove the existing file (will be directory) or
-       // must be directly modified (changed permissions or owner). In the
+       // file changes the new version will be written to a temporary file
+       // and then renamed "over" the original file. This is simple and
+       // prevents race conditions where the file is partially readable while
+       // changes to permissions or owner/group are applied. However, this
+       // strategy does not work for directories which must be removed first
+       // (was directory), must remove the existing file (will be directory)
+       // or must be directly modified (changed permissions or owner). In the
        // first two cases the old path is removed. In the last the directory
        // is modified (carefully) in place.
        //
@@ -132,6 +120,14 @@ func (s *Sync) syncFile(file *safcm.File, changed *bool) error {
 
        parentFd, baseName, err := OpenParentDirectoryNoSymlinks(file.Path)
        if err != nil {
+               if os.IsNotExist(err) && s.req.DryRun {
+                       change.Created = true
+                       debugf("will create (parent missing)")
+                       *changed = true
+                       debugf("dry-run, skipping changes")
+                       s.resp.FileChanges = append(s.resp.FileChanges, change)
+                       return nil
+               }
                return err
        }
        defer unix.Close(parentFd)
@@ -211,7 +207,9 @@ reopen:
                        // 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).
+                       // for symlink permissions (the value on GNU/Linux)
+                       // when comparing. The actual permissions on the file
+                       // system might be different on BSD systems.
                        //
                        // TODO: Add proper support for symlinks on BSD
                        change.Old.Mode |= 0777
@@ -322,7 +320,7 @@ reopen:
                // (accidentally) replacing a directory tree with a file.
                const msg = "will not replace non-empty directory, " +
                        "please remove manually"
-               err := unix.Unlinkat(parentFd, baseName, 0)
+               err := unix.Unlinkat(parentFd, baseName, 0 /* flags */)
                if err != nil && !os.IsNotExist(err) {
                        err2 := unix.Unlinkat(parentFd, baseName,
                                unix.AT_REMOVEDIR)
@@ -446,7 +444,7 @@ reopen:
                err = unix.Fchownat(parentFd, tmpBase, file.Uid, file.Gid,
                        unix.AT_SYMLINK_NOFOLLOW)
                if err != nil {
-                       unix.Unlinkat(parentFd, tmpBase, 0) //nolint:errcheck
+                       unix.Unlinkat(parentFd, tmpBase, 0 /* flags */) //nolint:errcheck
                        return err
                }
                // Permissions are irrelevant for symlinks (on most systems)
@@ -458,7 +456,7 @@ reopen:
        debugf("renaming %q", slashpath.Join(dir, tmpBase))
        err = unix.Renameat(parentFd, tmpBase, parentFd, baseName)
        if err != nil {
-               unix.Unlinkat(parentFd, tmpBase, 0) //nolint:errcheck
+               unix.Unlinkat(parentFd, tmpBase, 0 /* flags */) //nolint:errcheck
                return err
        }
        // To guarantee durability fsync must be called on a parent directory
@@ -561,7 +559,8 @@ func OpenParentDirectoryNoSymlinks(path string) (int, string, error) {
                }
        }
 
-       dirFd, err := unix.Openat(unix.AT_FDCWD, dir, openReadonlyFlags, 0)
+       dirFd, err := unix.Openat(unix.AT_FDCWD, dir,
+               openReadonlyFlags, 0 /* mode */)
        if err != nil {
                return -1, "", err
        }
@@ -641,7 +640,7 @@ func OpenFileNoSymlinks(path string) (*os.File, error) {
 }
 
 func OpenAtNoFollow(dirFd int, base string) (*os.File, error) {
-       fd, err := unix.Openat(dirFd, base, openReadonlyFlags, 0)
+       fd, err := unix.Openat(dirFd, base, openReadonlyFlags, 0 /* mode */)
        if err != nil {
                return nil, err
        }
@@ -659,7 +658,7 @@ func WriteTempAt(dirFd int, base string, data []byte, uid, gid int,
        _, err = fh.Write(data)
        if err != nil {
                fh.Close()
-               unix.Unlinkat(dirFd, tmpBase, 0) //nolint:errcheck
+               unix.Unlinkat(dirFd, tmpBase, 0 /* flags */) //nolint:errcheck
                return "", err
        }
        // createTempAt() creates the file with 0600