diff --git a/lib/fs.h b/lib/fs.h index 8c5d26f7f..d8490aa6a 100644 --- a/lib/fs.h +++ b/lib/fs.h @@ -108,6 +108,11 @@ #define DIR_IS_DOT(x) ((x)[0] == '.' && (x)[1] == '\0') #define DIR_IS_DOTDOT(x) ((x)[0] == '.' && (x)[1] == '.' && (x)[2] == '\0') +/* On macOS, if a file system doesn't support resource forks, metadata is automatically stored in + * shadow files beginning with `._`. They are automatically managed by the OS and will reappear if + * deleted. */ +#define FILE_IS_RESOURCE_FORK(x) ((x)[0] == '.' && (x)[1] == '_' && (x)[2] != '\0') + /*** enums ***************************************************************************************/ /*** structures declarations (and typedefs of structures)*****************************************/ diff --git a/src/filemanager/file.c b/src/filemanager/file.c index 398bfa85f..5a09f0bfa 100644 --- a/src/filemanager/file.c +++ b/src/filemanager/file.c @@ -1411,80 +1411,6 @@ erase_file (file_op_context_t *ctx, const vfs_path_t *vpath) /* --------------------------------------------------------------------------------------------- */ -static FileProgressStatus -try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath) -{ - FileProgressStatus return_status = FILE_CONT; - - while (mc_rmdir (vpath) != 0 && !ctx->ignore_all) - { - return_status = - file_error (ctx, TRUE, _ ("Cannot remove directory\n%s"), vfs_path_as_str (vpath)); - if (return_status == FILE_IGNORE_ALL) - ctx->ignore_all = TRUE; - if (return_status != FILE_RETRY) - break; - } - - return return_status; -} - -/* --------------------------------------------------------------------------------------------- */ - -/** - Recursive removal of files - abort -> cancel stack - ignore -> warn every level, gets default - ignore_all -> remove as much as possible -*/ -static FileProgressStatus -recursive_erase (file_op_context_t *ctx, const vfs_path_t *vpath) -{ - struct vfs_dirent *next; - DIR *reading; - FileProgressStatus return_status = FILE_CONT; - - reading = mc_opendir (vpath); - if (reading == NULL) - return FILE_RETRY; - - while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) - { - vfs_path_t *tmp_vpath; - struct stat buf; - - if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) - continue; - - tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL); - if (mc_lstat (tmp_vpath, &buf) != 0) - { - mc_closedir (reading); - vfs_path_free (tmp_vpath, TRUE); - return FILE_RETRY; - } - if (S_ISDIR (buf.st_mode)) - return_status = recursive_erase (ctx, tmp_vpath); - else - return_status = erase_file (ctx, tmp_vpath); - vfs_path_free (tmp_vpath, TRUE); - } - mc_closedir (reading); - - if (return_status == FILE_ABORT) - return FILE_ABORT; - - file_progress_show_deleting (ctx, vpath, NULL); - file_progress_show_count (ctx); - if (file_progress_check_buttons (ctx) == FILE_ABORT) - return FILE_ABORT; - - mc_refresh (); - - return try_erase_dir (ctx, vpath); -} - -/* --------------------------------------------------------------------------------------------- */ /** * Check if directory is empty or not. * @@ -1541,6 +1467,26 @@ check_dir_is_empty (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgres /* --------------------------------------------------------------------------------------------- */ +static FileProgressStatus +try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath) +{ + FileProgressStatus return_status = FILE_CONT; + + while (mc_rmdir (vpath) != 0 && !ctx->ignore_all) + { + return_status = + file_error (ctx, TRUE, _ ("Cannot remove directory\n%s"), vfs_path_as_str (vpath)); + if (return_status == FILE_IGNORE_ALL) + ctx->ignore_all = TRUE; + if (return_status != FILE_RETRY) + break; + } + + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ + static FileProgressStatus erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath) { @@ -1567,6 +1513,70 @@ erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath) /* --------------------------------------------------------------------------------------------- */ +/** + Recursive removal of files + abort -> cancel stack + ignore -> warn every level, gets default + ignore_all -> remove as much as possible + + This function should either be called with delete_resource_forks = TRUE always, or called twice, + first with FALSE and then with TRUE to make sure that no errors pop up due to resource forks being + deleted on macOS and then being re-created on the fly by the OS. +*/ +static FileProgressStatus +recursive_erase (file_op_context_t *ctx, const vfs_path_t *vpath, + const gboolean delete_resource_forks) +{ + struct vfs_dirent *next; + DIR *reading; + FileProgressStatus return_status = FILE_CONT; + + reading = mc_opendir (vpath); + if (reading == NULL) + return FILE_RETRY; + + while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) + { + vfs_path_t *tmp_vpath; + struct stat buf; + + if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) + continue; + + tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL); + if (mc_lstat (tmp_vpath, &buf) != 0) + { + mc_closedir (reading); + vfs_path_free (tmp_vpath, TRUE); + return FILE_RETRY; + } + if (S_ISDIR (buf.st_mode)) + return_status = recursive_erase (ctx, tmp_vpath, delete_resource_forks); + else if (delete_resource_forks || !FILE_IS_RESOURCE_FORK (next->d_name)) + return_status = erase_file (ctx, tmp_vpath); + else + return_status = FILE_SKIP; + + vfs_path_free (tmp_vpath, TRUE); + } + + mc_closedir (reading); + + if (return_status == FILE_ABORT) + return FILE_ABORT; + + file_progress_show_deleting (ctx, vpath, NULL); + file_progress_show_count (ctx); + if (file_progress_check_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + return erase_dir_iff_empty (ctx, vpath); +} + +/* --------------------------------------------------------------------------------------------- */ + static void erase_dir_after_copy (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status) { @@ -3390,7 +3400,11 @@ erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath) // not empty error = query_recursive (ctx, vfs_path_as_str (vpath)); if (error == FILE_CONT) - error = recursive_erase (ctx, vpath); + { + error = recursive_erase (ctx, vpath, FALSE); + if (error != FILE_ABORT) + error = recursive_erase (ctx, vpath, TRUE); + } return error; }