void files_chmod(FileView *view, const char *mode, int recurse_dirs) { int i; ui_cancellation_reset(); i = 0; while(i < view->list_rows && !view->dir_entry[i].selected) i++; if(i == view->list_rows) { char buf[COMMAND_GROUP_INFO_LEN]; char inv[16]; snprintf(buf, sizeof(buf), "chmod in %s: %s", replace_home_part(flist_get_dir(view)), view->dir_entry[view->list_pos].name); cmd_group_begin(buf); snprintf(inv, sizeof(inv), "0%o", view->dir_entry[view->list_pos].mode & 0xff); chmod_file_in_list(view, view->list_pos, mode, inv, recurse_dirs); } else { char buf[COMMAND_GROUP_INFO_LEN]; size_t len; int j = i; len = snprintf(buf, sizeof(buf), "chmod in %s: ", replace_home_part(flist_get_dir(view))); while(i < view->list_rows && len < sizeof(buf)) { if(view->dir_entry[i].selected) { if(len >= 2 && buf[len - 2] != ':') { strncat(buf + len, ", ", sizeof(buf) - len - 1); len += strlen(buf + len); } strncat(buf + len, view->dir_entry[i].name, sizeof(buf) - len - 1); len += strlen(buf + len); } i++; } cmd_group_begin(buf); while(j < view->list_rows && !ui_cancellation_requested()) { if(view->dir_entry[j].selected) { char inv[16]; snprintf(inv, sizeof(inv), "0%o", view->dir_entry[j].mode & 0xff); chmod_file_in_list(view, j, mode, inv, recurse_dirs); } j++; } } cmd_group_end(); }
/* changes attributes of files in the view */ static void files_attrib(FileView *view, DWORD add, DWORD sub, int recurse_dirs) { int i; ui_cancellation_reset(); i = 0; while(i < view->list_rows && !view->dir_entry[i].selected) i++; if(i == view->list_rows) { char buf[COMMAND_GROUP_INFO_LEN]; snprintf(buf, sizeof(buf), "chmod in %s: %s", replace_home_part(view->curr_dir), view->dir_entry[view->list_pos].name); cmd_group_begin(buf); attrib_file_in_list(view, view->list_pos, add, sub, recurse_dirs); } else { char buf[COMMAND_GROUP_INFO_LEN]; size_t len; int j = i; len = snprintf(buf, sizeof(buf), "chmod in %s: ", replace_home_part(view->curr_dir)); while(i < view->list_rows && len < sizeof(buf)) { if(view->dir_entry[i].selected) { if(len >= 2 && buf[len - 2] != ':') { strncat(buf + len, ", ", sizeof(buf) - len - 1); len += strlen(buf + len); } strncat(buf + len, view->dir_entry[i].name, sizeof(buf) - len - 1); len += strlen(buf + len); } i++; } cmd_group_begin(buf); while(j < view->list_rows && !ui_cancellation_requested()) { if(view->dir_entry[j].selected) { attrib_file_in_list(view, j, add, sub, recurse_dirs); } j++; } } cmd_group_end(); }
/* Collects files under specified file system tree. */ static void list_files_recursively(const char path[], int skip_dot_files, strlist_t *list) { int i; /* Obtain sorted list of files. */ int len; char **lst = list_sorted_files(path, &len); if(len < 0) { return; } /* Visit all subdirectories ignoring symbolic links to directories. */ for(i = 0; i < len && !ui_cancellation_requested(); ++i) { char *full_path; if(skip_dot_files && lst[i][0] == '.') { update_string(&lst[i], NULL); continue; } full_path = format_str("%s/%s", path, lst[i]); if(is_dir(full_path)) { if(!is_symlink(full_path)) { list_files_recursively(full_path, skip_dot_files, list); } free(full_path); update_string(&lst[i], NULL); } else { free(lst[i]); lst[i] = full_path; } show_progress("Listing...", 1000); } /* Append files. */ for(i = 0; i < len; ++i) { if(lst[i] != NULL) { list->nitems = put_into_string_array(&list->items, list->nitems, lst[i]); } } free(lst); }
int capture_output_to_menu(FileView *view, const char cmd[], menu_info *m) { FILE *file, *err; char *line = NULL; int x; pid_t pid; LOG_INFO_MSG("Capturing output of the command to a menu: %s", cmd); pid = background_and_capture((char *)cmd, &file, &err); if(pid == (pid_t)-1) { show_error_msgf("Trouble running command", "Unable to run: %s", cmd); return 0; } show_progress("", 0); ui_cancellation_reset(); ui_cancellation_enable(); wait_for_data_from(pid, file, 0); x = 0; while((line = read_line(file, line)) != NULL) { char *expanded_line; show_progress("Loading menu", 1000); m->items = realloc(m->items, sizeof(char *)*(x + 1)); expanded_line = expand_tabulation_a(line, cfg.tab_stop); if(expanded_line != NULL) { m->items[x++] = expanded_line; } wait_for_data_from(pid, file, 0); } m->len = x; ui_cancellation_disable(); fclose(file); print_errors(err); if(ui_cancellation_requested()) { append_to_string(&m->title, "(cancelled) "); append_to_string(&m->empty_msg, " (cancelled)"); } return display_menu(m, view); }
void files_chmod(FileView *view, const char *mode, int recurse_dirs) { char undo_msg[COMMAND_GROUP_INFO_LEN]; dir_entry_t *entry; size_t len; snprintf(undo_msg, sizeof(undo_msg), "chmod in %s: ", replace_home_part(flist_get_dir(view))); len = strlen(undo_msg); ui_cancellation_reset(); entry = NULL; while(iter_selection_or_current(view, &entry) && !ui_cancellation_requested()) { if(len >= 2U && undo_msg[len - 2U] != ':') { strncat(undo_msg + len, ", ", sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } strncat(undo_msg + len, entry->name, sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } cmd_group_begin(undo_msg); entry = NULL; while(iter_selection_or_current(view, &entry) && !ui_cancellation_requested()) { char inv[16]; snprintf(inv, sizeof(inv), "0%o", entry->mode & 0xff); chmod_file_in_list(view, entry_to_pos(view, entry), mode, inv, recurse_dirs); } cmd_group_end(); }
/* Changes attributes of files in the view. */ static void files_attrib(FileView *view, DWORD add, DWORD sub, int recurse_dirs) { char undo_msg[COMMAND_GROUP_INFO_LEN]; dir_entry_t *entry; size_t len; snprintf(undo_msg, sizeof(undo_msg), "chmod in %s: ", replace_home_part(flist_get_dir(view))); len = strlen(undo_msg); ui_cancellation_reset(); entry = NULL; while(iter_selection_or_current(view, &entry) && !ui_cancellation_requested()) { if(len >= 2U && undo_msg[len - 2U] != ':') { strncat(undo_msg + len, ", ", sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } strncat(undo_msg + len, entry->name, sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } cmd_group_begin(undo_msg); entry = NULL; while(iter_selection_or_current(view, &entry) && !ui_cancellation_requested()) { attrib_file_in_list(view, entry_to_pos(view, entry), add, sub, recurse_dirs); } cmd_group_end(); }
int capture_output_to_menu(FileView *view, const char cmd[], int user_sh, menu_state_t *m) { if(process_cmd_output("Loading menu", cmd, user_sh, 0, &output_handler, m->d) != 0) { show_error_msgf("Trouble running command", "Unable to run: %s", cmd); return 0; } if(ui_cancellation_requested()) { append_to_string(&m->d->title, "(cancelled)"); append_to_string(&m->d->empty_msg, " (cancelled)"); } return display_menu(m, view); }
/* Deletes current item from the trash. */ static KHandlerResponse delete_current(menu_data_t *m) { int ret; io_args_t args = { .arg1.path = trash_list[m->pos].trash_name, .cancellation.hook = &ui_cancellation_hook, }; ioe_errlst_init(&args.result.errors); ui_cancellation_enable(); ret = ior_rm(&args); ui_cancellation_disable(); if(ret != 0) { char *const errors = ioe_errlst_to_str(&args.result.errors); ioe_errlst_free(&args.result.errors); show_error_msg("File deletion error", errors); free(errors); return KHR_UNHANDLED; } ioe_errlst_free(&args.result.errors); remove_current_item(m->state); return KHR_REFRESH_WINDOW; } /* Implementation of cancellation hook for I/O unit. */ static int ui_cancellation_hook(void *arg) { return ui_cancellation_requested(); }
/* Checks status of the job. Processes error stream or checks whether process * is still running. */ static void job_check(job_t *const job) { #ifndef _WIN32 fd_set ready; int max_fd = 0; struct timeval ts = { .tv_sec = 0, .tv_usec = 1000 }; /* Setup pipe for reading */ FD_ZERO(&ready); if(job->fd >= 0) { FD_SET(job->fd, &ready); max_fd = job->fd; } if(job->error != NULL) { if(!job->skip_errors) { job->skip_errors = prompt_error_msg("Background Process Error", job->error); } free(job->error); job->error = NULL; } while(select(max_fd + 1, &ready, NULL, NULL, &ts) > 0) { char err_msg[ERR_MSG_LEN]; const ssize_t nread = read(job->fd, err_msg, sizeof(err_msg) - 1); if(nread == 0) { break; } else if(nread > 0 && !job->skip_errors) { err_msg[nread] = '\0'; job->skip_errors = prompt_error_msg("Background Process Error", err_msg); } } #else DWORD retcode; if(GetExitCodeProcess(job->hprocess, &retcode) != 0) { if(retcode != STILL_ACTIVE) { job->running = 0; } } #endif } /* Frees resources allocated by the job as well as the job_t structure itself. * The job can be NULL. */ static void job_free(job_t *const job) { if(job == NULL) { return; } if(job->type != BJT_COMMAND) { pthread_mutex_destroy(&job->bg_op_guard); } #ifndef _WIN32 if(job->fd != NO_JOB_ID) { close(job->fd); } #else if(job->hprocess != NO_JOB_ID) { CloseHandle(job->hprocess); } #endif free(job->bg_op.descr); free(job->cmd); free(job); } /* Used for FUSE mounting and unmounting only. */ int background_and_wait_for_status(char cmd[], int cancellable, int *cancelled) { #ifndef _WIN32 pid_t pid; int status; if(cancellable) { *cancelled = 0; } if(cmd == NULL) { return 1; } (void)set_sigchld(1); pid = fork(); if(pid == (pid_t)-1) { (void)set_sigchld(0); LOG_SERROR_MSG(errno, "Forking has failed."); return -1; } if(pid == (pid_t)0) { extern char **environ; (void)set_sigchld(0); (void)execve(get_execv_path(cfg.shell), make_execv_array(cfg.shell, cmd), environ); _Exit(127); } if(cancellable) { ui_cancellation_enable(); } while(waitpid(pid, &status, 0) == -1) { if(errno != EINTR) { LOG_SERROR_MSG(errno, "Failed waiting for process: %" PRINTF_ULL, (unsigned long long)pid); status = -1; break; } process_cancel_request(pid); } if(cancellable) { if(ui_cancellation_requested()) { *cancelled = 1; } ui_cancellation_disable(); } (void)set_sigchld(0); return status; #else return -1; #endif }
int compare_two_panes(CompareType ct, ListType lt, int group_paths, int skip_empty) { int next_id = 1; entries_t curr, other; trie_t *const trie = trie_create(); ui_cancellation_reset(); ui_cancellation_enable(); curr = make_diff_list(trie, curr_view, &next_id, ct, skip_empty, 0); other = make_diff_list(trie, other_view, &next_id, ct, skip_empty, lt == LT_DUPS); ui_cancellation_disable(); trie_free_with_data(trie, &free_compare_records); /* Clear progress message displayed by make_diff_list(). */ ui_sb_quick_msg_clear(); if(ui_cancellation_requested()) { free_dir_entries(curr_view, &curr.entries, &curr.nentries); free_dir_entries(other_view, &other.entries, &other.nentries); status_bar_message("Comparison has been cancelled"); return 1; } if(!group_paths || lt != LT_ALL) { /* Sort both lists according to unique file numbers to group identical files * (sorting is stable, tags are set in make_diff_list()). */ qsort(curr.entries, curr.nentries, sizeof(*curr.entries), &id_sorter); qsort(other.entries, other.nentries, sizeof(*other.entries), &id_sorter); } if(lt == LT_UNIQUE) { make_unique_lists(curr, other); return 0; } if(lt == LT_DUPS) { leave_only_dups(&curr, &other); } flist_custom_start(curr_view, lt == LT_ALL ? "diff" : "dups diff"); flist_custom_start(other_view, lt == LT_ALL ? "diff" : "dups diff"); fill_side_by_side(curr, other, group_paths); if(flist_custom_finish(curr_view, CV_DIFF, 0) != 0) { show_error_msg("Comparison", "No results to display"); return 0; } if(flist_custom_finish(other_view, CV_DIFF, 0) != 0) { assert(0 && "The error shouldn't be happening here."); } curr_view->list_pos = 0; other_view->list_pos = 0; curr_view->custom.diff_cmp_type = ct; other_view->custom.diff_cmp_type = ct; curr_view->custom.diff_path_group = group_paths; other_view->custom.diff_path_group = group_paths; assert(curr_view->list_rows == other_view->list_rows && "Diff views must be in sync!"); ui_view_schedule_redraw(curr_view); ui_view_schedule_redraw(other_view); return 0; }
/* Makes sorted by path list of entries that. The trie is used to keep track of * identical files. With non-zero dups_only, new files aren't added to the * trie. */ static entries_t make_diff_list(trie_t *trie, FileView *view, int *next_id, CompareType ct, int skip_empty, int dups_only) { int i; strlist_t files = {}; entries_t r = {}; int last_progress = 0; show_progress("Listing...", 0); if(flist_custom_active(view) && ONE_OF(view->custom.type, CV_REGULAR, CV_VERY)) { list_view_entries(view, &files); } else { list_files_recursively(flist_get_dir(view), view->hide_dot, &files); } show_progress("Querying...", 0); for(i = 0; i < files.nitems && !ui_cancellation_requested(); ++i) { char progress_msg[128]; int progress; int existing_id; char *fingerprint; const char *const path = files.items[i]; dir_entry_t *const entry = entry_list_add(view, &r.entries, &r.nentries, path); if(skip_empty && entry->size == 0) { free_dir_entry(view, entry); --r.nentries; continue; } fingerprint = get_file_fingerprint(path, entry, ct); /* In case we couldn't obtain fingerprint (e.g., comparing by contents and * files isn't readable), ignore the file and keep going. */ if(is_null_or_empty(fingerprint)) { free(fingerprint); free_dir_entry(view, entry); --r.nentries; continue; } entry->tag = i; if(get_file_id(trie, path, fingerprint, &existing_id, ct)) { entry->id = existing_id; } else if(dups_only) { entry->id = -1; } else { entry->id = *next_id; ++*next_id; put_file_id(trie, path, fingerprint, entry->id, ct); } free(fingerprint); progress = (i*100)/files.nitems; if(progress != last_progress) { last_progress = progress; snprintf(progress_msg, sizeof(progress_msg), "Querying... %d (% 2d%%)", i, progress); show_progress(progress_msg, -1); } } free_string_array(files.items, files.nitems); return r; }
int compare_one_pane(FileView *view, CompareType ct, ListType lt, int skip_empty) { int i, dup_id; FileView *other = (view == curr_view) ? other_view : curr_view; const char *const title = (lt == LT_ALL) ? "compare" : (lt == LT_DUPS) ? "dups" : "nondups"; int next_id = 1; entries_t curr; trie_t *trie = trie_create(); ui_cancellation_reset(); ui_cancellation_enable(); curr = make_diff_list(trie, view, &next_id, ct, skip_empty, 0); ui_cancellation_disable(); trie_free_with_data(trie, &free_compare_records); /* Clear progress message displayed by make_diff_list(). */ ui_sb_quick_msg_clear(); if(ui_cancellation_requested()) { free_dir_entries(view, &curr.entries, &curr.nentries); status_bar_message("Comparison has been cancelled"); return 1; } qsort(curr.entries, curr.nentries, sizeof(*curr.entries), &id_sorter); flist_custom_start(view, title); dup_id = -1; next_id = 0; for(i = 0; i < curr.nentries; ++i) { dir_entry_t *entry = &curr.entries[i]; if(lt == LT_ALL) { flist_custom_put(view, entry); continue; } if(entry->id == dup_id) { put_or_free(view, entry, next_id, lt == LT_DUPS); continue; } dup_id = (i < curr.nentries - 1 && entry[0].id == entry[1].id) ? entry->id : -1; if(entry->id == dup_id) { put_or_free(view, entry, ++next_id, lt == LT_DUPS); continue; } put_or_free(view, entry, next_id, lt == LT_UNIQUE); } /* Entries' data has been moved out of them or freed, so need to free only the * list. */ dynarray_free(curr.entries); if(flist_custom_finish(view, lt == LT_UNIQUE ? CV_REGULAR : CV_COMPARE, 0) != 0) { show_error_msg("Comparison", "No results to display"); return 0; } /* Leave the other pane, if it's in the CV_DIFF mode, two panes are needed for * this. */ if(other->custom.type == CV_DIFF) { cd_updir(other, 1); } view->list_pos = 0; ui_view_schedule_redraw(view); return 0; }
void wait_for_data_from(pid_t pid, FILE *f, int fd) { const struct timeval ts_init = { .tv_sec = 0, .tv_usec = 1000 }; struct timeval ts; fd_set read_ready; FD_ZERO(&read_ready); fd = (f != NULL) ? fileno(f) : fd; do { process_cancel_request(pid); ts = ts_init; FD_SET(fd, &read_ready); } while(select(fd + 1, &read_ready, NULL, NULL, &ts) == 0); /* Inform other parts of the application that cancellation took place. */ if(errno == EINTR) { ui_cancellation_request(); } } int set_sigchld(int block) { const int action = block ? SIG_BLOCK : SIG_UNBLOCK; sigset_t sigchld_mask; return sigemptyset(&sigchld_mask) == -1 || sigaddset(&sigchld_mask, SIGCHLD) == -1 || sigprocmask(action, &sigchld_mask, NULL) == -1; } void process_cancel_request(pid_t pid) { if(ui_cancellation_requested()) { if(kill(pid, SIGINT) != 0) { LOG_SERROR_MSG(errno, "Failed to send SIGINT to " PRINTF_PID_T, pid); } } } int get_proc_exit_status(pid_t pid) { do { int status; if(waitpid(pid, &status, 0) == -1) { if(errno != EINTR) { LOG_SERROR_MSG(errno, "waitpid()"); return -1; } } else { return status; } } while(1); } /* if err == 1 then use stderr and close stdin and stdout */ void _gnuc_noreturn run_from_fork(int pipe[2], int err, char *cmd) { char *args[4]; int nullfd; /* Redirect stderr or stdout to write end of pipe. */ if(dup2(pipe[1], err ? STDERR_FILENO : STDOUT_FILENO) == -1) { exit(1); } close(pipe[0]); /* Close read end of pipe. */ close(STDIN_FILENO); close(err ? STDOUT_FILENO : STDERR_FILENO); /* Send stdout, stdin to /dev/null */ if((nullfd = open("/dev/null", O_RDONLY)) != -1) { if(dup2(nullfd, STDIN_FILENO) == -1) exit(1); if(dup2(nullfd, err ? STDOUT_FILENO : STDERR_FILENO) == -1) exit(1); } args[0] = cfg.shell; args[1] = "-c"; args[2] = cmd; args[3] = NULL; execvp(args[0], args); exit(1); }
/* 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; }
int iop_cp(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; const int cancellable = args->cancellable; struct stat st; char block[BLOCK_SIZE]; FILE *in, *out; size_t nread; int error; struct stat src_st; const char *open_mode = "wb"; ioeta_update(args->estim, src, dst, 0, 0); #ifdef _WIN32 if(is_symlink(src) || crs != IO_CRS_APPEND_TO_FILES) { DWORD flags; int error; wchar_t *utf16_src, *utf16_dst; flags = COPY_FILE_COPY_SYMLINK; if(crs == IO_CRS_FAIL) { flags |= COPY_FILE_FAIL_IF_EXISTS; } else if(path_exists(dst, DEREF)) { /* Ask user whether to overwrite destination file. */ if(confirm != NULL && !confirm(args, src, dst)) { return 0; } } utf16_src = utf8_to_utf16(src); utf16_dst = utf8_to_utf16(dst); error = CopyFileExW(utf16_src, utf16_dst, &win_progress_cb, args, NULL, flags) == 0; if(error) { /* FIXME: use real system error message here. */ (void)ioe_errlst_append(&args->result.errors, dst, IO_ERR_UNKNOWN, "Copy file failed"); } free(utf16_src); free(utf16_dst); ioeta_update(args->estim, NULL, NULL, 1, 0); return error; } #endif /* Create symbolic link rather than copying file it points to. This check * should go before directory check as is_dir() resolves symbolic links. */ if(is_symlink(src)) { char link_target[PATH_MAX]; int error; io_args_t ln_args = { .arg1.path = link_target, .arg2.target = dst, .arg3.crs = crs, .cancellable = cancellable, .result = args->result, }; if(get_link_target(src, link_target, sizeof(link_target)) != 0) { (void)ioe_errlst_append(&args->result.errors, src, IO_ERR_UNKNOWN, "Failed to get symbolic link target"); return 1; } error = iop_ln(&ln_args); args->result = ln_args.result; if(error != 0) { (void)ioe_errlst_append(&args->result.errors, src, IO_ERR_UNKNOWN, "Failed to make symbolic link"); return 1; } return 0; } if(is_dir(src)) { (void)ioe_errlst_append(&args->result.errors, src, EISDIR, strerror(EISDIR)); return 1; } if(os_stat(src, &st) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return 1; } #ifndef _WIN32 /* Fifo/socket/device files don't need to be opened, their content is not * accessed. */ if(S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode) || S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) { in = NULL; } else #endif { in = os_fopen(src, "rb"); if(in == NULL) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return 1; } } if(crs == IO_CRS_APPEND_TO_FILES) { open_mode = "ab"; } else if(crs != IO_CRS_FAIL) { int ec; if(path_exists(dst, DEREF)) { /* Ask user whether to overwrite destination file. */ if(confirm != NULL && !confirm(args, src, dst)) { if(in != NULL && fclose(in) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } return 0; } } ec = unlink(dst); if(ec != 0 && errno != ENOENT) { (void)ioe_errlst_append(&args->result.errors, dst, errno, strerror(errno)); if(in != NULL && fclose(in) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } return ec; } /* XXX: possible improvement would be to generate temporary file name in the * destination directory, write to it and then overwrite destination file, * but this approach has disadvantage of requiring more free space on * destination file system. */ } else if(path_exists(dst, DEREF)) { (void)ioe_errlst_append(&args->result.errors, src, EEXIST, strerror(EEXIST)); if(in != NULL && fclose(in) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } return 1; } #ifndef _WIN32 /* Replicate fifo without even opening it. */ if(S_ISFIFO(st.st_mode)) { if(mkfifo(dst, st.st_mode & 07777) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return 1; } return 0; } /* Replicate socket or device file without even opening it. */ if(S_ISSOCK(st.st_mode) || S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) { if(mknod(dst, st.st_mode & (S_IFMT | 07777), st.st_rdev) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); return 1; } return 0; } #endif out = os_fopen(dst, open_mode); if(out == NULL) { (void)ioe_errlst_append(&args->result.errors, dst, errno, strerror(errno)); if(fclose(in) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } return 1; } error = 0; if(crs == IO_CRS_APPEND_TO_FILES) { fpos_t pos; /* The following line is required for stupid Windows sometimes. Why? * Probably because it's stupid... Won't harm other systems. */ fseek(out, 0, SEEK_END); error = fgetpos(out, &pos) != 0 || fsetpos(in, &pos) != 0; if(!error) { ioeta_update(args->estim, NULL, NULL, 0, get_file_size(dst)); } } /* TODO: use sendfile() if platform supports it. */ while((nread = fread(&block, 1, sizeof(block), in)) != 0U) { if(cancellable && ui_cancellation_requested()) { error = 1; break; } if(fwrite(&block, 1, nread, out) != nread) { (void)ioe_errlst_append(&args->result.errors, dst, errno, strerror(errno)); error = 1; break; } ioeta_update(args->estim, NULL, NULL, 0, nread); } if(nread == 0U && !feof(in) && ferror(in)) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } if(fclose(in) != 0) { (void)ioe_errlst_append(&args->result.errors, src, errno, strerror(errno)); } if(fclose(out) != 0) { (void)ioe_errlst_append(&args->result.errors, dst, errno, strerror(errno)); } if(error == 0 && os_lstat(src, &src_st) == 0) { error = os_chmod(dst, src_st.st_mode & 07777); if(error != 0) { (void)ioe_errlst_append(&args->result.errors, dst, errno, strerror(errno)); } } ioeta_update(args->estim, NULL, NULL, 1, 0); return error; } #ifdef _WIN32 static DWORD CALLBACK win_progress_cb(LARGE_INTEGER total, LARGE_INTEGER transferred, LARGE_INTEGER stream_size, LARGE_INTEGER stream_transfered, DWORD stream_num, DWORD reason, HANDLE src_file, HANDLE dst_file, LPVOID param) { static LONGLONG last_size; io_args_t *const args = param; const char *const src = args->arg1.src; const char *const dst = args->arg2.dst; ioeta_estim_t *const estim = args->estim; if(transferred.QuadPart < last_size) { last_size = 0; } ioeta_update(estim, src, dst, 0, transferred.QuadPart - last_size); last_size = transferred.QuadPart; if(args->cancellable && ui_cancellation_requested()) { return PROGRESS_CANCEL; } return PROGRESS_CONTINUE; } #endif /* TODO: implement iop_chown(). */ int iop_chown(io_args_t *const args); /* TODO: implement iop_chgrp(). */ int iop_chgrp(io_args_t *const args); /* TODO: implement iop_chmod(). */ int iop_chmod(io_args_t *const args); int iop_ln(io_args_t *const args) { const char *const path = args->arg1.path; const char *const target = args->arg2.target; const int overwrite = args->arg3.crs != IO_CRS_FAIL; int result; #ifdef _WIN32 char cmd[6 + PATH_MAX*2 + 1]; char *escaped_path, *escaped_target; char base_dir[PATH_MAX + 2]; #endif #ifndef _WIN32 result = symlink(path, target); if(result != 0 && errno == EEXIST && overwrite && is_symlink(target)) { result = remove(target); if(result == 0) { result = symlink(path, target); if(result != 0) { (void)ioe_errlst_append(&args->result.errors, path, errno, strerror(errno)); } } else { (void)ioe_errlst_append(&args->result.errors, target, errno, strerror(errno)); } } else if(result != 0 && errno != 0) { (void)ioe_errlst_append(&args->result.errors, target, errno, strerror(errno)); } #else if(!overwrite && path_exists(target, DEREF)) { (void)ioe_errlst_append(&args->result.errors, target, EEXIST, strerror(EEXIST)); return -1; } if(overwrite && !is_symlink(target)) { (void)ioe_errlst_append(&args->result.errors, target, IO_ERR_UNKNOWN, "Target is not a symbolic link"); return -1; } escaped_path = shell_like_escape(path, 0); escaped_target = shell_like_escape(target, 0); if(escaped_path == NULL || escaped_target == NULL) { (void)ioe_errlst_append(&args->result.errors, target, IO_ERR_UNKNOWN, "Not enough memory"); free(escaped_target); free(escaped_path); return -1; } if(GetModuleFileNameA(NULL, base_dir, ARRAY_LEN(base_dir)) == 0) { (void)ioe_errlst_append(&args->result.errors, target, IO_ERR_UNKNOWN, "Failed to find win_helper"); free(escaped_target); free(escaped_path); return -1; } break_atr(base_dir, '\\'); snprintf(cmd, sizeof(cmd), "%s\\win_helper -s %s %s", base_dir, escaped_path, escaped_target); result = os_system(cmd); if(result != 0) { (void)ioe_errlst_append(&args->result.errors, target, IO_ERR_UNKNOWN, "Running win_helper has failed"); } free(escaped_target); free(escaped_path); #endif return result; }