static void create_directory(const char path[], const char root[], int create_parents) { assert_int_equal(-1, access(path, F_OK)); { io_args_t args = { .arg1.path = path, .arg2.process_parents = create_parents, .arg3.mode = 0700, }; assert_int_equal(0, iop_mkdir(&args)); } assert_int_equal(0, access(path, F_OK)); assert_true(is_dir(path)); { io_args_t args = { .arg1.path = root, }; assert_int_equal(0, ior_rm(&args)); } } TEST(child_dir_is_not_created) { assert_int_equal(-1, access(NESTED_DIR_NAME, F_OK)); { io_args_t args = { .arg1.path = NESTED_DIR_NAME, .arg2.process_parents = 0, }; assert_false(iop_mkdir(&args) == 0); } assert_int_equal(-1, access(NESTED_DIR_NAME, F_OK)); } TEST(permissions_are_taken_into_account, IF(has_unix_permissions)) { { io_args_t args = { .arg1.path = DIR_NAME, .arg2.process_parents = 0, .arg3.mode = 0000, }; assert_int_equal(0, iop_mkdir(&args)); } { io_args_t args = { .arg1.path = NESTED_DIR_NAME, .arg2.process_parents = 0, }; assert_false(iop_mkdir(&args) == 0); } { io_args_t args = { .arg1.path = DIR_NAME, }; assert_int_equal(0, iop_rmdir(&args)); } } TEST(permissions_are_taken_into_account_for_the_most_nested_only, IF(has_unix_permissions)) { { io_args_t args = { .arg1.path = NESTED_DIR_NAME, .arg2.process_parents = 1, .arg3.mode = 0000, }; assert_int_equal(0, iop_mkdir(&args)); } { io_args_t args = { .arg1.path = DIR_NAME "/dir", .arg2.process_parents = 0, .arg3.mode = 0755, }; assert_int_equal(0, iop_mkdir(&args)); } { io_args_t args = { .arg1.path = NESTED_DIR_NAME "/dir", .arg2.process_parents = 0, .arg3.mode = 0755, }; assert_false(iop_mkdir(&args) == 0); } { io_args_t args = { .arg1.path = NESTED_DIR_NAME, }; assert_int_equal(0, iop_rmdir(&args)); } { io_args_t args = { .arg1.path = DIR_NAME "/dir", }; assert_int_equal(0, iop_rmdir(&args)); } { io_args_t args = { .arg1.path = DIR_NAME, }; assert_int_equal(0, iop_rmdir(&args)); } } static int has_unix_permissions(void) { #if defined(_WIN32) || defined(__CYGWIN__) return 0; #else return 1; #endif }
/* Implementation of traverse() visitor for subtree removal. Returns 0 on * success, otherwise non-zero is returned. */ static VisitResult rm_visitor(const char full_path[], VisitAction action, void *param) { io_args_t *const rm_args = param; VisitResult result = VR_OK; if(rm_args->cancellable && ui_cancellation_requested()) { return VR_CANCELLED; } switch(action) { case VA_DIR_ENTER: /* Do nothing, directories are removed on leaving them. */ result = VR_OK; break; case VA_FILE: { io_args_t args = { .arg1.path = full_path, .cancellable = rm_args->cancellable, .estim = rm_args->estim, .result = rm_args->result, }; result = (iop_rmfile(&args) == 0) ? VR_OK : VR_ERROR; rm_args->result = args.result; break; } case VA_DIR_LEAVE: { io_args_t args = { .arg1.path = full_path, .cancellable = rm_args->cancellable, .estim = rm_args->estim, .result = rm_args->result, }; result = (iop_rmdir(&args) == 0) ? VR_OK : VR_ERROR; rm_args->result = args.result; break; } } return result; } int ior_cp(io_args_t *const args) { const char *const src = args->arg1.src; const char *const dst = args->arg2.dst; if(is_in_subtree(dst, src)) { (void)ioe_errlst_append(&args->result.errors, src, IO_ERR_UNKNOWN, "Can't copy parent path into subpath"); return 1; } if(args->arg3.crs == IO_CRS_REPLACE_ALL) { io_args_t rm_args = { .arg1.path = dst, .cancellable = args->cancellable, .estim = args->estim, .result = args->result, }; const int result = ior_rm(&rm_args); args->result = rm_args.result; if(result != 0) { if(!args->cancellable || !ui_cancellation_requested()) { (void)ioe_errlst_append(&args->result.errors, dst, IO_ERR_UNKNOWN, "Failed to remove"); } return result; } } return traverse(src, &cp_visitor, args); } /* Implementation of traverse() visitor for subtree copying. Returns 0 on * success, otherwise non-zero is returned. */ static VisitResult cp_visitor(const char full_path[], VisitAction action, void *param) { return cp_mv_visitor(full_path, action, param, 1); } int ior_mv(io_args_t *const args) { const char *const src = args->arg1.src; const char *const dst = args->arg2.dst; const IoCrs crs = args->arg3.crs; const io_confirm confirm = args->confirm; if(crs == IO_CRS_FAIL && path_exists(dst, DEREF) && !is_case_change(src, dst)) { (void)ioe_errlst_append(&args->result.errors, dst, EEXIST, strerror(EEXIST)); return 1; } if(crs == IO_CRS_APPEND_TO_FILES) { if(!is_file(src)) { (void)ioe_errlst_append(&args->result.errors, src, EISDIR, strerror(EISDIR)); return 1; } if(!is_file(dst)) { (void)ioe_errlst_append(&args->result.errors, dst, EISDIR, strerror(EISDIR)); return 1; } } else if(crs == IO_CRS_REPLACE_FILES && path_exists(dst, DEREF)) { /* Ask user whether to overwrite destination file. */ if(confirm != NULL && !confirm(args, src, dst)) { return 0; } } if(os_rename(src, dst) == 0) { ioeta_update(args->estim, src, dst, 1, 0); return 0; } switch(errno) { case EXDEV: { int result = ior_cp(args); if(result == 0) { io_args_t rm_args = { .arg1.path = src, .cancellable = args->cancellable, .estim = args->estim, .result = args->result, }; /* Disable progress reporting for this "secondary" operation. */ const int silent = ioeta_silent_on(rm_args.estim); result = ior_rm(&rm_args); args->result = rm_args.result; ioeta_silent_set(rm_args.estim, silent); } return result; } case EISDIR: case ENOTEMPTY: case EEXIST: #ifdef _WIN32 /* For MXE builds running in Wine. */ case EPERM: case EACCES: #endif if(crs == IO_CRS_REPLACE_ALL) { int error; io_args_t rm_args = { .arg1.path = dst, .cancellable = args->cancellable, .estim = args->estim, .result = args->result, }; /* Ask user whether to overwrite destination file. */ if(confirm != NULL && !confirm(args, src, dst)) { return 0; } error = ior_rm(&rm_args); args->result = rm_args.result; if(error != 0) { if(!args->cancellable || !ui_cancellation_requested()) { (void)ioe_errlst_append(&args->result.errors, dst, IO_ERR_UNKNOWN, "Failed to remove"); } return error; } if(os_rename(src, dst) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return 1; } return 0; } else if(crs == IO_CRS_REPLACE_FILES || (!has_atomic_file_replace() && crs == IO_CRS_APPEND_TO_FILES)) { if(!has_atomic_file_replace() && is_file(dst)) { io_args_t rm_args = { .arg1.path = dst, .cancellable = args->cancellable, .estim = args->estim, .result = args->result, }; const int error = iop_rmfile(&rm_args); args->result = rm_args.result; if(error != 0) { if(!args->cancellable || !ui_cancellation_requested()) { (void)ioe_errlst_append(&args->result.errors, dst, IO_ERR_UNKNOWN, "Failed to remove"); } return error; } } return traverse(src, &mv_visitor, args); } /* Break is intentionally omitted. */ default: (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return errno; } } /* Checks that path points to a file or symbolic link. Returns non-zero if so, * otherwise zero is returned. */ static int is_file(const char path[]) { return !is_dir(path) || (is_symlink(path) && get_symlink_type(path) != SLT_UNKNOWN); } /* Implementation of traverse() visitor for subtree moving. Returns 0 on * success, otherwise non-zero is returned. */ static VisitResult mv_visitor(const char full_path[], VisitAction action, void *param) { return cp_mv_visitor(full_path, action, param, 0); } /* Generic implementation of traverse() visitor for subtree copying/moving. * Returns 0 on success, otherwise non-zero is returned. */ static VisitResult cp_mv_visitor(const char full_path[], VisitAction action, void *param, int cp) { io_args_t *const cp_args = param; const char *dst_full_path; char *free_me = NULL; VisitResult result = VR_OK; const char *rel_part; if(cp_args->cancellable && ui_cancellation_requested()) { return VR_CANCELLED; } /* TODO: come up with something better than this. */ rel_part = full_path + strlen(cp_args->arg1.src); dst_full_path = (rel_part[0] == '\0') ? cp_args->arg2.dst : (free_me = format_str("%s/%s", cp_args->arg2.dst, rel_part)); switch(action) { case VA_DIR_ENTER: if(cp_args->arg3.crs != IO_CRS_REPLACE_FILES || !is_dir(dst_full_path)) { io_args_t args = { .arg1.path = dst_full_path, /* Temporary fake rights so we can add files to the directory. */ .arg3.mode = 0700, .cancellable = cp_args->cancellable, .estim = cp_args->estim, .result = cp_args->result, }; result = (iop_mkdir(&args) == 0) ? VR_OK : VR_ERROR; cp_args->result = args.result; } break; case VA_FILE: { io_args_t args = { .arg1.src = full_path, .arg2.dst = dst_full_path, .arg3.crs = cp_args->arg3.crs, /* It's safe to always use fast file cloning on moving files. */ .arg4.fast_file_cloning = cp ? cp_args->arg4.fast_file_cloning : 1, .cancellable = cp_args->cancellable, .confirm = cp_args->confirm, .estim = cp_args->estim, .result = cp_args->result, }; result = ((cp ? iop_cp(&args) : ior_mv(&args)) == 0) ? VR_OK : VR_ERROR; cp_args->result = args.result; break; } case VA_DIR_LEAVE: { struct stat st; if(cp_args->arg3.crs == IO_CRS_REPLACE_FILES && !cp) { io_args_t rm_args = { .arg1.path = full_path, .cancellable = cp_args->cancellable, .estim = cp_args->estim, .result = cp_args->result, }; result = (iop_rmdir(&rm_args) == 0) ? VR_OK : VR_ERROR; } else if(os_stat(full_path, &st) == 0) { result = (os_chmod(dst_full_path, st.st_mode & 07777) == 0) ? VR_OK : VR_ERROR; if(result == VR_ERROR) { (void)ioe_errlst_append(&cp_args->result.errors, dst_full_path, errno, strerror(errno)); } clone_timestamps(dst_full_path, full_path, &st); } else { (void)ioe_errlst_append(&cp_args->result.errors, full_path, errno, strerror(errno)); result = VR_ERROR; } break; } } free(free_me); return result; }