Пример #1
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();
}
Пример #2
0
Файл: mv.c Проект: KryDos/vifm
static void
test_file_is_moved(void)
{
	create_empty_file("binary-data");

	{
		io_args_t args =
		{
			.arg1.src = "binary-data",
			.arg2.dst = "moved-binary-data",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_false(access("binary-data", F_OK) == 0);
	assert_int_equal(0, access("moved-binary-data", F_OK));

	remove("moved-binary-data");
}

static void
test_empty_directory_is_moved(void)
{
	create_empty_dir("empty-dir");

	{
		io_args_t args =
		{
			.arg1.src = "empty-dir",
			.arg2.dst = "moved-empty-dir",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_false(is_dir("empty-dir"));
	assert_true(is_dir("moved-empty-dir"));

	{
		io_args_t args =
		{
			.arg1.path = "moved-empty-dir",
		};
		assert_int_equal(0, iop_rmdir(&args));
	}
}

static void
test_non_empty_directory_is_moved(void)
{
	create_non_empty_dir("non-empty-dir", "a-file");

	{
		io_args_t args =
		{
			.arg1.src = "non-empty-dir",
			.arg2.dst = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_int_equal(0, access("moved-non-empty-dir/a-file", F_OK));

	{
		io_args_t args =
		{
			.arg1.path = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_empty_nested_directory_is_moved(void)
{
	create_empty_nested_dir("non-empty-dir", "empty-nested-dir");

	{
		io_args_t args =
		{
			.arg1.src = "non-empty-dir",
			.arg2.dst = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_true(is_dir("moved-non-empty-dir/empty-nested-dir"));

	{
		io_args_t args =
		{
			.arg1.path = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_non_empty_nested_directory_is_moved(void)
{
	create_non_empty_nested_dir("non-empty-dir", "nested-dir", "a-file");

	{
		io_args_t args =
		{
			.arg1.src = "non-empty-dir",
			.arg2.dst = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_false(access("non-empty-dir/nested-dir/a-file", F_OK) == 0);
	assert_int_equal(0, access("moved-non-empty-dir/nested-dir/a-file", F_OK));

	{
		io_args_t args =
		{
			.arg1.path = "moved-non-empty-dir",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_fails_to_overwrite_file_by_default(void)
{
	create_empty_file("a-file");

	{
		io_args_t args =
		{
			.arg1.src = "../read/two-lines",
			.arg2.dst = "a-file",
		};
		assert_false(ior_mv(&args) == 0);
	}

	{
		io_args_t args =
		{
			.arg1.path = "a-file",
		};
		assert_int_equal(0, iop_rmfile(&args));
	}
}

static void
test_fails_to_overwrite_dir_by_default(void)
{
	create_empty_dir("empty-dir");

	{
		io_args_t args =
		{
			.arg1.src = "../read",
			.arg2.dst = "empty-dir",
		};
		assert_false(ior_mv(&args) == 0);
	}

	{
		io_args_t args =
		{
			.arg1.path = "empty-dir",
		};
		assert_int_equal(0, iop_rmdir(&args));
	}
}

static void
test_overwrites_file_when_asked(void)
{
	create_empty_file("a-file");

	{
		io_args_t args =
		{
			.arg1.src = "../read/two-lines",
			.arg2.dst = "two-lines",
		};
		assert_int_equal(0, iop_cp(&args));
	}

	{
		io_args_t args =
		{
			.arg1.src = "two-lines",
			.arg2.dst = "a-file",
			.arg3.crs = IO_CRS_REPLACE_FILES,
		};
		assert_int_equal(0, ior_mv(&args));
	}

	{
		io_args_t args =
		{
			.arg1.path = "a-file",
		};
		assert_int_equal(0, iop_rmfile(&args));
	}
}

static void
test_overwrites_dir_when_asked(void)
{
	create_empty_dir("dir");

	{
		io_args_t args =
		{
			.arg1.src = "../read",
			.arg2.dst = "read",
		};
		assert_int_equal(0, ior_cp(&args));
	}

	{
		io_args_t args =
		{
			.arg1.src = "read",
			.arg2.dst = "dir",
			.arg3.crs = IO_CRS_REPLACE_ALL,
		};
		assert_int_equal(0, ior_mv(&args));
	}

	{
		io_args_t args =
		{
			.arg1.path = "dir",
		};
		assert_false(iop_rmdir(&args) == 0);
	}

	{
		io_args_t args =
		{
			.arg1.path = "dir",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_appending_fails_for_directories(void)
{
	create_empty_dir("dir");

	{
		io_args_t args =
		{
			.arg1.src = "../read",
			.arg2.dst = "read",
		};
		assert_int_equal(0, ior_cp(&args));
	}

	{
		io_args_t args =
		{
			.arg1.src = "read",
			.arg2.dst = "dir",
			.arg3.crs = IO_CRS_APPEND_TO_FILES,
		};
		assert_false(ior_mv(&args) == 0);
	}

	{
		io_args_t args =
		{
			.arg1.path = "dir",
		};
		assert_int_equal(0, iop_rmdir(&args));
	}

	{
		io_args_t args =
		{
			.arg1.path = "read",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_appending_works_for_files(void)
{
	uint64_t size;

	{
		io_args_t args =
		{
			.arg1.src = "../read/two-lines",
			.arg2.dst = "two-lines",
		};
		assert_int_equal(0, iop_cp(&args));
	}

	size = get_file_size("two-lines");

	{
		io_args_t args =
		{
			.arg1.src = "../read/two-lines",
			.arg2.dst = "two-lines2",
		};
		assert_int_equal(0, iop_cp(&args));
	}

	{
		io_args_t args =
		{
			.arg1.src = "two-lines2",
			.arg2.dst = "two-lines",
			.arg3.crs = IO_CRS_APPEND_TO_FILES,
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_int_equal(size, get_file_size("two-lines"));

	{
		io_args_t args =
		{
			.arg1.path = "two-lines",
		};
		assert_int_equal(0, iop_rmfile(&args));
	}
}

static void
test_directories_can_be_merged(void)
{
	create_empty_dir("first");

	assert_int_equal(0, chdir("first"));
	create_empty_file("first-file");
	assert_int_equal(0, chdir(".."));

	create_empty_dir("second");

	assert_int_equal(0, chdir("second"));
	create_empty_file("second-file");
	assert_int_equal(0, chdir(".."));

	{
		io_args_t args =
		{
			.arg1.src = "first",
			.arg2.dst = "second",
			.arg3.crs = IO_CRS_REPLACE_FILES,
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_int_equal(0, access("second/second-file", F_OK));
	assert_int_equal(0, access("second/first-file", F_OK));

	{
		io_args_t args =
		{
			.arg1.path = "first",
		};
		assert_int_equal(0, ior_rm(&args));
	}

	{
		io_args_t args =
		{
			.arg1.path = "second",
		};
		assert_int_equal(0, ior_rm(&args));
	}
}

static void
test_fails_to_move_directory_inside_itself(void)
{
	create_empty_dir("empty-dir");

	{
		io_args_t args =
		{
			.arg1.src = "empty-dir",
			.arg2.dst = "empty-dir/empty-dir-copy",
		};
		assert_false(ior_mv(&args) == 0);
	}

	{
		io_args_t args =
		{
			.arg1.path = "empty-dir",
		};
		assert_int_equal(0, iop_rmdir(&args));
	}
}

#ifndef _WIN32

static void
test_symlink_is_symlink_after_move(void)
{
	{
		io_args_t args =
		{
			.arg1.path = "../read/two-lines",
			.arg2.target = "sym-link",
		};
		assert_int_equal(0, iop_ln(&args));
	}

	assert_true(is_symlink("sym-link"));

	{
		io_args_t args =
		{
			.arg1.src = "sym-link",
			.arg2.dst = "moved-sym-link",
		};
		assert_int_equal(0, ior_mv(&args));
	}

	assert_true(!is_symlink("sym-link"));
	assert_true(is_symlink("moved-sym-link"));

	{
		io_args_t args =
		{
			.arg1.path = "moved-sym-link",
		};
		assert_int_equal(0, iop_rmfile(&args));
	}
}

#endif

void
mv_tests(void)
{
	test_fixture_start();

	run_test(test_file_is_moved);
	run_test(test_empty_directory_is_moved);
	run_test(test_non_empty_directory_is_moved);
	run_test(test_empty_nested_directory_is_moved);
	run_test(test_non_empty_nested_directory_is_moved);
	run_test(test_fails_to_overwrite_file_by_default);
	run_test(test_fails_to_overwrite_dir_by_default);
	run_test(test_overwrites_file_when_asked);
	run_test(test_overwrites_dir_when_asked);
	run_test(test_appending_fails_for_directories);
	run_test(test_appending_works_for_files);
	run_test(test_directories_can_be_merged);
	run_test(test_fails_to_move_directory_inside_itself);

#ifndef _WIN32
	/* Creating symbolic links on Windows requires administrator rights. */
	run_test(test_symlink_is_symlink_after_move);
#endif

	test_fixture_end();
}
Пример #3
0
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
}
Пример #4
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;
}