diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index f4e291385b..00a6a37ff7 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1613,7 +1613,7 @@ int btrfs_subvol_snapshot_fd_full( return -EISDIR; r = btrfs_subvol_make(new_path); - if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { + if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */ if (mkdir(new_path, 0755) < 0) return -errno; @@ -1624,8 +1624,15 @@ int btrfs_subvol_snapshot_fd_full( r = copy_directory_fd_full( old_fd, new_path, - COPY_MERGE|COPY_REFLINK|COPY_SAME_MOUNT|COPY_HARDLINKS|(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0), - progress_path, progress_bytes, userdata); + COPY_MERGE_EMPTY| + COPY_REFLINK| + COPY_SAME_MOUNT| + COPY_HARDLINKS| + (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0)| + (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGTERM) ? COPY_SIGTERM : 0), + progress_path, + progress_bytes, + userdata); if (r < 0) goto fallback_fail; diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 7b18f57719..b67a4c10fe 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -35,6 +35,7 @@ typedef enum BtrfsSnapshotFlags { BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 1 << 4, /* If the destination doesn't support subvolumes, reflink/copy instead */ BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 1 << 5, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */ BTRFS_SNAPSHOT_SIGINT = 1 << 6, /* Check for SIGINT regularly, and return EINTR if seen */ + BTRFS_SNAPSHOT_SIGTERM = 1 << 7, /* Ditto, but for SIGTERM */ } BtrfsSnapshotFlags; typedef enum BtrfsRemoveFlags { diff --git a/src/shared/copy.c b/src/shared/copy.c index 65d75a15d5..a01b6df1b2 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -88,6 +88,23 @@ static int fd_is_nonblock_pipe(int fd) { return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE; } +static int look_for_signals(CopyFlags copy_flags) { + int r; + + if ((copy_flags & (COPY_SIGINT|COPY_SIGTERM)) == 0) + return 0; + + r = pop_pending_signal(copy_flags & COPY_SIGINT ? SIGINT : 0, + copy_flags & COPY_SIGTERM ? SIGTERM : 0); + if (r < 0) + return r; + if (r != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINTR), + "Got %s, cancelling copy operation.", signal_to_string(r)); + + return 0; +} + int copy_bytes_full( int fdf, int fdt, uint64_t max_bytes, @@ -176,13 +193,9 @@ int copy_bytes_full( if (max_bytes <= 0) return 1; /* return > 0 if we hit the max_bytes limit */ - if (FLAGS_SET(copy_flags, COPY_SIGINT)) { - r = pop_pending_signal(SIGINT); - if (r < 0) - return r; - if (r > 0) - return -EINTR; - } + r = look_for_signals(copy_flags); + if (r < 0) + return r; if (max_bytes != UINT64_MAX && m > max_bytes) m = max_bytes; @@ -627,10 +640,8 @@ static int fd_copy_regular( return -errno; r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata); - if (r < 0) { - (void) unlinkat(dt, to, 0); - return r; - } + if (r < 0) + goto fail; if (fchown(fdt, uid_is_valid(override_uid) ? override_uid : st->st_uid, @@ -643,16 +654,25 @@ static int fd_copy_regular( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); (void) copy_xattr(fdf, fdt); - q = close(fdt); - fdt = -1; + if (copy_flags & COPY_FSYNC) { + if (fsync(fdt) < 0) { + r = -errno; + goto fail; + } + } + q = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ if (q < 0) { - r = -errno; - (void) unlinkat(dt, to, 0); + r = q; + goto fail; } (void) memorize_hardlink(hardlink_context, st, dt, to); return r; + +fail: + (void) unlinkat(dt, to, 0); + return r; } static int fd_copy_fifo( @@ -845,13 +865,9 @@ static int fd_copy_directory( if (dot_or_dot_dot(de->d_name)) continue; - if (FLAGS_SET(copy_flags, COPY_SIGINT)) { - r = pop_pending_signal(SIGINT); - if (r < 0) - return r; - if (r > 0) - return -EINTR; - } + r = look_for_signals(copy_flags); + if (r < 0) + return r; if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { r = -errno; @@ -912,7 +928,7 @@ static int fd_copy_directory( else q = -EOPNOTSUPP; - if (q == -EINTR) /* Propagate SIGINT up instantly */ + if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */ return q; if (q == -EEXIST && (copy_flags & COPY_MERGE)) q = 0; @@ -933,6 +949,11 @@ static int fd_copy_directory( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); } + if (copy_flags & COPY_FSYNC_FULL) { + if (fsync(fdt) < 0) + return -errno; + } + return r; } @@ -949,6 +970,7 @@ int copy_tree_at_full( void *userdata) { struct stat st; + int r; assert(from); assert(to); @@ -957,17 +979,47 @@ int copy_tree_at_full( return -errno; if (S_ISREG(st.st_mode)) - return fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata); + r = fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); + r = fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); + r = fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) - return fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); + r = fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); else return -EOPNOTSUPP; + if (r < 0) + return r; + + if (S_ISDIR(st.st_mode) && (copy_flags & COPY_SYNCFS)) { + /* If the top-level inode is a directory run syncfs() now. */ + r = syncfs_path(fdt, to); + if (r < 0) + return r; + } else if ((copy_flags & (COPY_FSYNC_FULL|COPY_SYNCFS)) != 0) { + /* fsync() the parent dir of what we just copied if COPY_FSYNC_FULL is set. Also do this in + * case COPY_SYNCFS is set but the top-level inode wasn't actually a directory. We do this so that + * COPY_SYNCFS provides reasonable synchronization semantics on any kind of inode: when the + * copy operation is done the whole inode — regardless of its type — and all its children + * will be synchronized to disk. */ + r = fsync_parent_at(fdt, to); + if (r < 0) + return r; + } + + return 0; +} + +static int sync_dir_by_flags(const char *path, CopyFlags copy_flags) { + + if (copy_flags & COPY_SYNCFS) + return syncfs_path(AT_FDCWD, path); + if (copy_flags & COPY_FSYNC_FULL) + return fsync_parent_at(AT_FDCWD, path); + + return 0; } int copy_directory_fd_full( @@ -991,7 +1043,26 @@ int copy_directory_fd_full( if (r < 0) return r; - return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory( + dirfd, NULL, + &st, + AT_FDCWD, to, + st.st_dev, + COPY_DEPTH_MAX, + UID_INVALID, GID_INVALID, + copy_flags, + NULL, NULL, + progress_path, + progress_bytes, + userdata); + if (r < 0) + return r; + + r = sync_dir_by_flags(to, copy_flags); + if (r < 0) + return r; + + return 0; } int copy_directory_full( @@ -1015,7 +1086,26 @@ int copy_directory_full( if (r < 0) return r; - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory( + AT_FDCWD, from, + &st, + AT_FDCWD, to, + st.st_dev, + COPY_DEPTH_MAX, + UID_INVALID, GID_INVALID, + copy_flags, + NULL, NULL, + progress_path, + progress_bytes, + userdata); + if (r < 0) + return r; + + r = sync_dir_by_flags(to, copy_flags); + if (r < 0) + return r; + + return 0; } int copy_file_fd_full( @@ -1026,6 +1116,7 @@ int copy_file_fd_full( void *userdata) { _cleanup_close_ int fdf = -1; + struct stat st; int r; assert(from); @@ -1035,12 +1126,32 @@ int copy_file_fd_full( if (fdf < 0) return -errno; + r = fd_verify_regular(fdf); + if (r < 0) + return r; + + if (fstat(fdt, &st) < 0) + return -errno; + r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata); + if (r < 0) + return r; - (void) copy_times(fdf, fdt, copy_flags); - (void) copy_xattr(fdf, fdt); + if (S_ISREG(fdt)) { + (void) copy_times(fdf, fdt, copy_flags); + (void) copy_xattr(fdf, fdt); + } - return r; + if (copy_flags & COPY_FSYNC_FULL) { + r = fsync_full(fdt); + if (r < 0) + return r; + } else if (copy_flags & COPY_FSYNC) { + if (fsync(fdt) < 0) + return -errno; + } + + return 0; } int copy_file_full( @@ -1054,9 +1165,9 @@ int copy_file_full( copy_progress_bytes_t progress_bytes, void *userdata) { - _cleanup_close_ int fdf = -1; + _cleanup_close_ int fdf = -1, fdt = -1; struct stat st; - int r, fdt = -1; /* avoid false maybe-uninitialized warning */ + int r; assert(from); assert(to); @@ -1065,9 +1176,12 @@ int copy_file_full( if (fdf < 0) return -errno; - if (mode == MODE_INVALID) - if (fstat(fdf, &st) < 0) - return -errno; + if (fstat(fdf, &st) < 0) + return -errno; + + r = stat_verify_regular(&st); + if (r < 0) + return r; RUN_WITH_UMASK(0000) { if (copy_flags & COPY_MAC_CREATE) { @@ -1083,15 +1197,18 @@ int copy_file_full( return -errno; } + if (!FLAGS_SET(flags, O_EXCL)) { /* if O_EXCL was used we created the thing as regular file, no need to check again */ + r = fd_verify_regular(fdt); + if (r < 0) + goto fail; + } + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL); r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata); - if (r < 0) { - close(fdt); - (void) unlink(to); - return r; - } + if (r < 0) + goto fail; (void) copy_times(fdf, fdt, copy_flags); (void) copy_xattr(fdf, fdt); @@ -1099,12 +1216,31 @@ int copy_file_full( if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL); - if (close(fdt) < 0) { - unlink_noerrno(to); - return -errno; + if (copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL)) { + if (fsync(fdt) < 0) { + r = -errno; + goto fail; + } + } + + r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ + if (r < 0) + goto fail; + + if (copy_flags & COPY_FSYNC_FULL) { + r = fsync_parent_at(AT_FDCWD, to); + if (r < 0) + goto fail; } return 0; + +fail: + /* Only unlink if we definitely are the ones who created the file */ + if (FLAGS_SET(flags, O_EXCL)) + (void) unlink(to); + + return r; } int copy_file_atomic_full( @@ -1172,6 +1308,12 @@ int copy_file_atomic_full( if (fchmod(fdt, mode) < 0) return -errno; + if ((copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL))) { + /* Sync the file */ + if (fsync(fdt) < 0) + return -errno; + } + if (copy_flags & COPY_REPLACE) { if (renameat(AT_FDCWD, t, AT_FDCWD, to) < 0) return -errno; @@ -1181,11 +1323,27 @@ int copy_file_atomic_full( return r; } + t = mfree(t); + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL); - t = mfree(t); + r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ + if (r < 0) + goto fail; + + if (copy_flags & COPY_FSYNC_FULL) { + /* Sync the parent directory */ + r = fsync_parent_at(AT_FDCWD, to); + if (r < 0) + goto fail; + } + return 0; + +fail: + (void) unlink(to); + return r; } int copy_times(int fdf, int fdt, CopyFlags flags) { diff --git a/src/shared/copy.h b/src/shared/copy.h index b36ddfcb01..f8992843e4 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -10,15 +10,19 @@ #include typedef enum CopyFlags { - COPY_REFLINK = 1 << 0, /* Try to reflink */ - COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */ - COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */ - COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */ - COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */ - COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */ - COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */ - COPY_MAC_CREATE = 1 << 7, /* Create files with the correct MAC label (currently SELinux only) */ - COPY_HARDLINKS = 1 << 8, /* Try to reproduce hard links */ + COPY_REFLINK = 1 << 0, /* Try to reflink */ + COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */ + COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */ + COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */ + COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */ + COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */ + COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */ + COPY_SIGTERM = 1 << 7, /* ditto, but for SIGTERM */ + COPY_MAC_CREATE = 1 << 8, /* Create files with the correct MAC label (currently SELinux only) */ + COPY_HARDLINKS = 1 << 9, /* Try to reproduce hard links */ + COPY_FSYNC = 1 << 10, /* fsync() after we are done */ + COPY_FSYNC_FULL = 1 << 11, /* fsync_full() after we are done */ + COPY_SYNCFS = 1 << 12, /* syncfs() the *top-level* dir after we are done */ } CopyFlags; typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);