Exemple #1
0
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();
}
Exemple #2
0
/* 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();
}
Exemple #3
0
/* 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);
}
Exemple #4
0
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);
}
Exemple #5
0
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();
}
Exemple #6
0
/* 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();
}
Exemple #7
0
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);
}
Exemple #8
0
/* 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();
}
Exemple #9
0
/* 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
}
Exemple #10
0
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;
}
Exemple #11
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;
}
Exemple #12
0
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;
}
Exemple #13
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);
}
Exemple #14
0
/* 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;
}
Exemple #15
0
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;
}