/** * copy a directory between branches (includes all contents of the directory) */ int copy_directory(const char *path, int branch_ro, int branch_rw) { DBG_IN(); /* create the directory on the destination branch */ int res = path_create(path, branch_ro, branch_rw); if (res != 0) { return res; } /* determine path to source directory on read-only branch */ char from[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) return 1; DIR *dp = opendir(from); if (dp == NULL) return 1; struct dirent *de; while ((de = readdir(dp)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; char member[PATHLEN_MAX]; if (BUILD_PATH(member, path, de->d_name)) return 1; res = cow_cp(member, branch_ro, branch_rw); if (res != 0) return res; } closedir(dp); return 0; }
/** * Create a file or directory that hides path below root_rw */ static int do_create_whiteout(const char *path, int root_rw, enum whiteout mode) { char metapath[PATHLEN_MAX]; int res = -1; to_root(); // whiteouts are root business if (BUILD_PATH(metapath, METADIR, path)) { syslog (LOG_WARNING, "%s(): Path too long\n", __func__); goto out; } // p MUST be without path to branch prefix here! 2 x root_rw is correct here! // this creates e.g. branch/.unionfs/some_directory path_create_cutlast(metapath, root_rw, root_rw); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.roots[root_rw].path, metapath, HIDETAG)) { syslog (LOG_WARNING, "%s(): Path too long\n", __func__); goto out; } if (mode == WHITEOUT_FILE) { res = open(p, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (res == -1) goto out; res = close(res); } else res = mkdir(p, S_IRWXU); out: to_user(); return res; }
/** * copy a directory between branches (includes all contents of the directory) */ int copy_directory(const char *path, int branch_ro, int branch_rw) { DBG("%s\n", path); /* create the directory on the destination branch */ int res = path_create(path, branch_ro, branch_rw); if (res != 0) { RETURN(res); } /* determine path to source directory on read-only branch */ char from[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) RETURN(1); DIR *dp = opendir(from); if (dp == NULL) RETURN(1); struct dirent *de; while ((de = readdir(dp)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; char member[PATHLEN_MAX]; if (BUILD_PATH(member, path, "/", de->d_name)) { res = 1; break; } res = cow_cp(member, branch_ro, branch_rw, true); if (res != 0) break; } closedir(dp); struct stat buf; lstat(from, &buf); setfile(from, &buf); RETURN(res); }
/** * Create a file or directory that hides path below branch_rw */ static int do_create_whiteout(const char *path, int branch_rw, enum whiteout mode) { DBG("%s\n", path); char metapath[PATHLEN_MAX]; if (BUILD_PATH(metapath, METADIR, path)) RETURN(-1); // p MUST be without path to branch prefix here! 2 x branch_rw is correct here! // this creates e.g. branch/.unionfs/some_directory path_create_cutlast(metapath, branch_rw, branch_rw); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch_rw].path, metapath)) RETURN(-1); strcat(p, HIDETAG); // TODO check length int res; if (mode == WHITEOUT_FILE) { res = open(p, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (res == -1) RETURN(-1); res = close(res); } else { res = mkdir(p, S_IRWXU); if (res) USYSLOG(LOG_ERR, "Creating %s failed: %s\n", p, strerror(errno)); } RETURN(res); }
/** * initiate the cow-copy action */ int cow_cp(const char *path, int branch_ro, int branch_rw, bool copy_dir) { DBG("%s\n", path); // create the path to the file path_create_cutlast(path, branch_ro, branch_rw); char from[PATHLEN_MAX], to[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(to, uopt.branches[branch_rw].path, path)) RETURN(-ENAMETOOLONG); setlocale(LC_ALL, ""); struct cow cow; cow.uid = getuid(); // Copy the umask for explicit mode setting. cow.umask = umask(0); umask(cow.umask); cow.from_path = from; cow.to_path = to; struct stat buf; lstat(cow.from_path, &buf); cow.stat = &buf; int res; switch (buf.st_mode & S_IFMT) { case S_IFLNK: res = copy_link(&cow); break; case S_IFDIR: if (copy_dir) { res = copy_directory(path, branch_ro, branch_rw); } else { res = path_create(path, branch_ro, branch_rw); } break; case S_IFBLK: case S_IFCHR: res = copy_special(&cow); break; case S_IFIFO: res = copy_fifo(&cow); break; case S_IFSOCK: USYSLOG(LOG_WARNING, "COW of sockets not supported: %s\n", cow.from_path); RETURN(1); default: res = copy_file(&cow); } RETURN(res); }
/** * unionfs implementation of the create call * libfuse will call this to create regular files */ static int unionfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); // NOTE: We should do: // Create the file with mode=0 first, otherwise we might create // a file as root + x-bit + suid bit set, which might be used for // security racing! int res = open(p, fi->flags, 0); if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode fchmod(res, mode); fi->fh = res; remove_hidden(path, i); DBG("fd = %" PRIx64 "\n", fi->fh); RETURN(0); }
static int unionfs_open(const char *path, struct fuse_file_info *fi) { DBG("%s\n", path); int i; if (fi->flags & (O_WRONLY | O_RDWR)) { i = find_rw_branch_cutlast(path); } else { i = find_rorw_branch(path); } if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int fd = open(p, fi->flags); if (fd == -1) RETURN(-errno); if (fi->flags & (O_WRONLY | O_RDWR)) { // There might have been a hide file, but since we successfully // wrote to the real file, a hide file must not exist anymore remove_hidden(path, i); } // This makes exec() fail //fi->direct_io = 1; fi->fh = (unsigned long)fd; DBG("fd = %"PRIx64"\n", fi->fh); RETURN(0); }
/** * l_nbranch (lower nbranch than nbranch) is write protected, create the dir path on * nbranch for an other COW operation. */ int path_create(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[nbranch_rw].path, path)) RETURN(-ENAMETOOLONG); struct stat st; if (!stat(p, &st)) { // path does already exists, no need to create it RETURN(0); } char *walk = (char *)path; // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ while (*walk == '/') walk++; do { // walk over the directory name, walk will now be /dir2 while (*walk != '\0' && *walk != '/') walk++; // +1 due to \0, which gets added automatically snprintf(p, (walk - path) + 1, "%s", path); // walk - path = strlen(/dir1) int res = do_create(p, nbranch_ro, nbranch_rw); if (res) RETURN(res); // creating the directory failed // as above the do loop, walk over the next slashes, walk = dir2/ while (*walk == '/') walk++; } while (*walk != '\0'); RETURN(0); }
/** * Remove a hide-file in all roots up to maxroot * If maxroot == -1, try to delete it in all roots. */ int remove_hidden(const char *path, int maxroot) { if (!uopt.cow_enabled) return 0; if (maxroot == -1) maxroot = uopt.nroots; int i; for (i = 0; i <= maxroot; i++) { char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.roots[i].path, METADIR, path, HIDETAG)) { syslog(LOG_WARNING, "%s: Path too long\n", __func__); return 1; } struct stat buf; int res = lstat(p, &buf); if (res == -1) continue; switch (buf.st_mode & S_IFMT) { case S_IFDIR: rmdir(p); break; default: unlink(p); break; } } return 0; }
/** * check if any dir or file within path is hidden */ int path_hidden(const char *path, int branch) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(false); char whiteoutpath[PATHLEN_MAX]; if (BUILD_PATH(whiteoutpath, uopt.branches[branch].path, METADIR, path)) RETURN(false); // -1 as we MUST not end on the next path element char *walk = whiteoutpath + uopt.branches[branch].path_len + strlen(METADIR) - 1; // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ while (*walk == '/') walk++; do { // walk over the directory name, walk will now be /dir2 while (*walk != '\0' && *walk != '/') walk++; // +1 due to \0, which gets added automatically char p[PATHLEN_MAX]; // walk - path = strlen(/dir1) snprintf(p, (walk - whiteoutpath) + 1, "%s", whiteoutpath); int res = filedir_hidden(p); if (res) RETURN(res); // path is hidden or error // as above the do loop, walk over the next slashes, walk = dir2/ while (*walk == '/') walk++; } while (*walk != '\0'); RETURN(0); }
static int unionfs_getattr(const char *path, struct stat *stbuf) { DBG("%s\n", path); if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) { memset(stbuf, 0, sizeof(stbuf)); stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = STATS_SIZE; RETURN(0); } int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lstat(p, stbuf); if (res == -1) RETURN(-errno); /* This is a workaround for broken gnu find implementations. Actually, * n_links is not defined at all for directories by posix. However, it * seems to be common for filesystems to set it to one if the actual value * is unknown. Since nlink_t is unsigned and since these broken implementations * always substract 2 (for . and ..) this will cause an underflow, setting * it to max(nlink_t). */ if (S_ISDIR(stbuf->st_mode)) stbuf->st_nlink = 1; RETURN(0); }
/** * This method is to post-process options once we know all of them */ void unionfs_post_opts(void) { // chdir to the given chroot, we if (uopt.chroot) { int res = chdir(uopt.chroot); if (res) { fprintf(stderr, "Chdir to %s failed: %s ! Aborting!\n", uopt.chroot, strerror(errno)); exit(1); } } // Make the pathes absolute and add trailing slashes int i; for (i = 0; i<uopt.nbranches; i++) { // if -ochroot= is specified, the path has to be given absolute // or relative to the chroot, so no need to make it absolute // also won't work, since we are not yet in the chroot here if (!uopt.chroot) { uopt.branches[i].path = make_absolute(uopt.branches[i].path); } uopt.branches[i].path = add_trailing_slash(uopt.branches[i].path); // Prevent accidental umounts. Especially system shutdown scripts tend // to umount everything they can. If we don't have an open file descriptor, // this might cause unexpected behaviour. char path[PATHLEN_MAX]; if (!uopt.chroot) { BUILD_PATH(path, uopt.branches[i].path); } else { BUILD_PATH(path, uopt.chroot, uopt.branches[i].path); } int fd = open(path, O_RDONLY); if (fd == -1) { fprintf(stderr, "\nFailed to open %s: %s. Aborting!\n\n", path, strerror(errno)); exit(1); } uopt.branches[i].fd = fd; uopt.branches[i].path_len = strlen(path); } }
static int unionfs_chmod(const char *path, mode_t mode) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = chmod(p, mode); if (res == -1) RETURN(-errno); RETURN(0); }
static int unionfs_chown(const char *path, uid_t uid, gid_t gid) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lchown(p, uid, gid); if (res == -1) RETURN(-errno); RETURN(0); }
static int unionfs_link(const char *from, const char *to) { DBG("from %s to %s\n", from, to); // hardlinks do not work across different filesystems so we need a copy of from first int i = find_rw_branch_cow(from); if (i == -1) RETURN(-errno); int j = __find_rw_branch_cutlast(to, i); if (j == -1) RETURN(-errno); DBG("from branch: %d to branch: %d\n", i, j); char f[PATHLEN_MAX], t[PATHLEN_MAX]; if (BUILD_PATH(f, uopt.branches[i].path, from)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(t, uopt.branches[j].path, to)) RETURN(-ENAMETOOLONG); int res = link(f, t); if (res == -1) RETURN(-errno); // no need for set_owner(), since owner and permissions are copied over by link() remove_hidden(to, i); // remove hide file (if any) RETURN(0); }
static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lsetxattr(p, name, value, size, flags); if (res == -1) RETURN(-errno); RETURN(res); }
static int unionfs_listxattr(const char *path, char *list, size_t size) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = llistxattr(p, list, size); if (res == -1) RETURN(-errno); RETURN(res); }
static int unionfs_removexattr(const char *path, const char *name) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lremovexattr(p, name); if (res == -1) RETURN(-errno); RETURN(res); }
static int unionfs_truncate(const char *path, off_t size) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = truncate(p, size); if (res == -1) RETURN(-errno); RETURN(0); }
static int unionfs_symlink(const char *from, const char *to) { DBG("from %s to %s\n", from, to); int i = find_rw_branch_cutlast(to); if (i == -1) RETURN(-errno); char t[PATHLEN_MAX]; if (BUILD_PATH(t, uopt.branches[i].path, to)) RETURN(-ENAMETOOLONG); int res = symlink(from, t); if (res == -1) RETURN(-errno); set_owner(t); // no error check, since creating the file succeeded remove_hidden(to, i); // remove hide file (if any) RETURN(0); }
static int unionfs_readlink(const char *path, char *buf, size_t size) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = readlink(p, buf, size - 1); if (res == -1) RETURN(-errno); buf[res] = '\0'; RETURN(0); }
static int unionfs_utimens(const char *path, const struct timespec ts[2]) { DBG("%s\n", path); if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) RETURN(0); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = utimensat(0, p, ts, AT_SYMLINK_NOFOLLOW); if (res == -1) RETURN(-errno); RETURN(0); }
/** * unionfs mkdir() implementation * * NOTE: Never delete whiteouts directories here, since this will just * make already hidden sub-branches visible again. */ static int unionfs_mkdir(const char *path, mode_t mode) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = mkdir(p, 0); if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode chmod(p, mode); RETURN(0); }
/** * l_nbranch (lower nbranch than nbranch) is write protected, create the dir path on * nbranch for an other COW operation. */ int path_create(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[nbranch_rw].path, path)) RETURN(-ENAMETOOLONG); struct stat st; if (!stat(p, &st)) { // path does already exists, no need to create it RETURN(0); } char *walk = (char *)path; int res = path_create_by_step(path, walk, nbranch_ro, nbranch_rw); RETURN(res); }
static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) { #else static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { #endif DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #if __APPLE__ int res = setxattr(p, name, value, size, position, flags | XATTR_NOFOLLOW); #else int res = lsetxattr(p, name, value, size, flags); #endif if (res == -1) RETURN(-errno); RETURN(res); }
/** * Remove a hide-file in all branches up to maxbranch * If maxbranch == -1, try to delete it in all branches. */ int remove_hidden(const char *path, int maxbranch) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); if (maxbranch == -1) maxbranch = uopt.nbranches; int i; for (i = 0; i <= maxbranch; i++) { char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, METADIR, path)) RETURN(-ENAMETOOLONG); if (strlen(p) + strlen(HIDETAG) > PATHLEN_MAX) RETURN(-ENAMETOOLONG); strcat(p, HIDETAG); // TODO check length switch (path_is_dir(p)) { case IS_FILE: unlink(p); break; case IS_DIR: rmdir(p); break; case NOT_EXISTING: continue; } } RETURN(0); }
static int unionfs_mknod(const char *path, mode_t mode, dev_t rdev) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int file_type = mode & S_IFMT; int file_perm = mode & (S_PROT_MASK); int res = -1; if ((file_type) == S_IFREG) { // under FreeBSD, only the super-user can create ordinary files using mknod // Actually this workaround should not be required any more // since we now have the unionfs_create() method // So can we remove it? USYSLOG (LOG_INFO, "deprecated mknod workaround, tell the unionfs-fuse authors if you see this!\n"); res = creat(p, 0); if (res > 0 && close(res) == -1) USYSLOG(LOG_WARNING, "Warning, cannot close file\n"); } else { res = mknod(p, file_type, rdev); } if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode chmod(p, file_perm); remove_hidden(path, i); RETURN(0); }
/** * check if any dir or file within path is hidden */ bool path_hidden(const char *path, int branch) { if (!uopt.cow_enabled) return false; char whiteoutpath[PATHLEN_MAX]; if (BUILD_PATH(whiteoutpath, uopt.roots[branch].path, METADIR, path)) { syslog (LOG_WARNING, "%s(): Path too long\n", __func__); return false; } char *walk = whiteoutpath; // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ while (*walk != '\0' && *walk == '/') walk++; bool first = true; do { // walk over the directory name, walk will now be /dir2 while (*walk != '\0' && *walk != '/') walk++; if (first) { // first dir in path is our branch, no need to check if it is hidden first = false; continue; } // +1 due to \0, which gets added automatically char p[PATHLEN_MAX]; snprintf(p, (walk - whiteoutpath) + 1, "%s", whiteoutpath); // walk - path = strlen(/dir1) bool res = filedir_hidden(p); if (res) return res; // path is hidden // as above the do loop, walk over the next slashes, walk = dir2/ while (*walk != '\0' && *walk == '/') walk++; } while (*walk != '\0'); return 0; }
static int unionfs_utimens(const char *path, const struct timespec ts[2]) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #ifdef UNIONFS_HAVE_AT int res = utimensat(0, p, ts, AT_SYMLINK_NOFOLLOW); #else struct timeval tv[2]; tv[0].tv_sec = ts[0].tv_sec; tv[0].tv_usec = ts[0].tv_nsec / 1000; tv[1].tv_sec = ts[1].tv_sec; tv[1].tv_usec = ts[1].tv_nsec / 1000; int res = utimes(p, tv); #endif if (res == -1) RETURN(-errno); RETURN(0); }
/** * unionfs rename function * TODO: If we rename a directory on a read-only branch, we need to copy over * all files to the renamed directory on the read-write branch. */ static int unionfs_rename(const char *from, const char *to) { DBG("from %s to %s\n", from, to); bool is_dir = false; // is 'from' a file or directory int j = find_rw_branch_cutlast(to); if (j == -1) RETURN(-errno); int i = find_rorw_branch(from); if (i == -1) RETURN(-errno); if (!uopt.branches[i].rw) { i = find_rw_branch_cow_common(from, true); if (i == -1) RETURN(-errno); } if (i != j) { USYSLOG(LOG_ERR, "%s: from and to are on different writable branches %d vs %d, which" "is not supported yet.\n", __func__, i, j); RETURN(-EXDEV); } char f[PATHLEN_MAX], t[PATHLEN_MAX]; if (BUILD_PATH(f, uopt.branches[i].path, from)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(t, uopt.branches[i].path, to)) RETURN(-ENAMETOOLONG); filetype_t ftype = path_is_dir(f); if (ftype == NOT_EXISTING) RETURN(-ENOENT); else if (ftype == IS_DIR) is_dir = true; int res; if (!uopt.branches[i].rw) { // since original file is on a read-only branch, we copied the from file to a writable branch, // but since we will rename from, we also need to hide the from file on the read-only branch if (is_dir) res = hide_dir(from, i); else res = hide_file(from, i); if (res) RETURN(-errno); } res = rename(f, t); if (res == -1) { int err = errno; // unlink() might overwrite errno // if from was on a read-only branch we copied it, but now rename failed so we need to delete it if (!uopt.branches[i].rw) { if (unlink(f)) USYSLOG(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now " "also unlink() failed\n", __func__, from); if (remove_hidden(from, i)) USYSLOG(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now " "also removing the whiteout failed\n", __func__, from); } RETURN(-err); } if (uopt.branches[i].rw) { // A lower branch still *might* have a file called 'from', we need to delete this. // We only need to do this if we have been on a rw-branch, since we created // a whiteout for read-only branches anyway. if (is_dir) maybe_whiteout(from, i, WHITEOUT_DIR); else maybe_whiteout(from, i, WHITEOUT_FILE); } remove_hidden(to, i); // remove hide file (if any) RETURN(0); }