X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;f=remote%2Fsync%2Ffiles.go;h=9b72bbcacc9644b0eaca4990ab08e12213385e5e;hb=HEAD;hp=20f8505c0fff92a27f638575bda6a0c611ce2ad5;hpb=d109a6151f48fc9e322bacbd3cc5e4f00eade713;p=safcm%2Fsafcm.git diff --git a/remote/sync/files.go b/remote/sync/files.go index 20f8505..9b72bbc 100644 --- a/remote/sync/files.go +++ b/remote/sync/files.go @@ -1,19 +1,7 @@ // MsgSyncReq: copy files to the remote host -// Copyright (C) 2021-2023 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 . +// 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