diff --git a/src/shared/copy.c b/src/shared/copy.c index abebe2d2f2..8af1cb5ce5 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -765,6 +765,35 @@ static int memorize_hardlink( return 1; } +static int prepare_nocow(int fdf, const char *from, int fdt, unsigned *chattr_mask, unsigned *chattr_flags) { + unsigned attrs = 0; + int r; + + assert(fdf >= 0 || fdf == AT_FDCWD); + assert(fdt >= 0); + assert(!!chattr_mask == !!chattr_flags); + + /* If caller explicitly requested NOCOW to be set or unset, let's not interfere. */ + if (chattr_mask && FLAGS_SET(*chattr_mask, FS_NOCOW_FL)) + return 0; + + r = read_attr_at(fdf, from, &attrs); + if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r) && r != -ELOOP) /* If the source is a symlink we get ELOOP */ + return r; + + if (FLAGS_SET(attrs, FS_NOCOW_FL)) { + if (chattr_mask && chattr_flags) { + *chattr_mask |= FS_NOCOW_FL; + *chattr_flags |= FS_NOCOW_FL; + } else + /* If the NOCOW flag is set on the source, make the copy NOCOW as well. If the source + * is not NOCOW, don't do anything in particular with the copy. */ + (void) chattr_fd(fdt, FS_NOCOW_FL, FS_NOCOW_FL, /*previous=*/ NULL); + } + + return 0; +} + static int fd_copy_tree_generic( int df, const char *from, @@ -824,6 +853,10 @@ static int fd_copy_regular( if (fdt < 0) return -errno; + r = prepare_nocow(fdf, /*from=*/ NULL, fdt, /*chattr_mask=*/ NULL, /*chattr_flags=*/ NULL); + if (r < 0) + return r; + r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata); if (r < 0) goto fail; @@ -1452,6 +1485,10 @@ int copy_file_at_full( goto fail; } + r = prepare_nocow(fdf, /*from=*/ NULL, fdt, &chattr_mask, &chattr_flags); + if (r < 0) + return r; + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL); @@ -1530,6 +1567,10 @@ int copy_file_atomic_at_full( if (fdt < 0) return fdt; + r = prepare_nocow(dir_fdf, from, fdt, &chattr_mask, &chattr_flags); + if (r < 0) + return r; + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);