1/* srm */ 2/* Copyright (c) 2000 Matthew D. Gauthier 3 * Portions copyright (c) 2007 Apple Inc. All rights reserved. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 * IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 * OTHER DEALINGS IN THE SOFTWARE. 23 * 24 * Except as contained in this notice, the name of the contributors shall 25 * not be used in advertising or otherwise to promote the sale, use or 26 * other dealings in this Software without prior written authorization. 27 */ 28 29#include <errno.h> 30#include <fcntl.h> 31#include <stdlib.h> 32#include <stdio.h> 33#include <string.h> 34#include <sys/stat.h> 35#include <sys/types.h> 36#include <unistd.h> 37#include <fts.h> 38 39#include "removefile.h" 40#include "removefile_priv.h" 41 42int 43__removefile_process_file(FTS* stream, FTSENT* current_file, removefile_state_t state) { 44 int res = 0; 45 char* path = current_file->fts_path; 46 47 int recursive = state->unlink_flags & REMOVEFILE_RECURSIVE; 48 int keep_parent = state->unlink_flags & REMOVEFILE_KEEP_PARENT; 49 int secure = state->unlink_flags & (REMOVEFILE_SECURE_7_PASS | REMOVEFILE_SECURE_35_PASS | REMOVEFILE_SECURE_1_PASS | REMOVEFILE_SECURE_3_PASS | REMOVEFILE_SECURE_1_PASS_ZERO); 50 51 switch (current_file->fts_info) { 52 // attempt to unlink the directory on pre-order in case it is 53 // a directory hard-link. If we succeed, it was a hard-link 54 // and we should not descend any further. 55 case FTS_D: 56 if (unlink(path) == 0) { 57 fts_set(stream, current_file, FTS_SKIP); 58 } 59 break; 60 case FTS_DC: 61 state->error_num = ELOOP; 62 res = -1; 63 break; 64 case FTS_DNR: 65 case FTS_ERR: 66 case FTS_NS: 67 state->error_num = current_file->fts_errno; 68 res = -1; 69 break; 70 case FTS_DP: 71 if (recursive && 72 (!keep_parent || 73 current_file->fts_level != FTS_ROOTLEVEL)) { 74 if (secure) { 75 res = __removefile_rename_unlink(path, 76 state); 77 } else { 78 if (geteuid() == 0 && 79 (current_file->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 80 !(current_file->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 81 chflags(path, current_file->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0) { 82 errno = EACCES; 83 res = -1; 84 } else { 85 res = rmdir(path); 86 } 87 } 88 if (res == -1) state->error_num = errno; 89 } 90 break; 91 case FTS_F: 92 case FTS_SL: 93 case FTS_SLNONE: 94 case FTS_DEFAULT: 95 if (secure) { 96 res = __removefile_sunlink(path, state); 97 } else if (geteuid() == 0 && 98 (current_file->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 99 !(current_file->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 100 chflags(path, current_file->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0) { 101 errno = EACCES; 102 res = -1; 103 } else { 104 res = unlink(path); 105 } 106 if (res == -1) state->error_num = errno; 107 break; 108 case FTS_DOT: 109 default: 110 break; 111 } 112 return res; 113} 114 115int 116__removefile_tree_walker(char **trees, removefile_state_t state) { 117 FTSENT *current_file; 118 FTS *stream; 119 int rval = 0; 120 121 removefile_callback_t cb_confirm = NULL; 122 removefile_callback_t cb_status = NULL; 123 removefile_callback_t cb_error = NULL; 124 125 cb_confirm = state->confirm_callback; 126 cb_status = state->status_callback; 127 cb_error = state->error_callback; 128 129 stream = fts_open(trees, FTS_PHYSICAL | FTS_NOCHDIR, NULL); 130 if (stream == NULL) return -1; 131 132 while ((current_file = fts_read(stream)) != NULL) { 133 int res = REMOVEFILE_PROCEED; 134 135 /* 136 * fts_number is set to REMOVEFILE_SKIP for directories where 137 * the confirmation callback has asked to skip it. We check 138 * this on post-order, so we don't remove an empty directory that 139 * the caller wanted preserved. 140 */ 141 if (current_file->fts_info == FTS_DP && 142 current_file->fts_number == REMOVEFILE_SKIP) { 143 current_file->fts_number = 0; 144 continue; 145 } 146 147 // don't process the file if a cancel has been requested 148 if (__removefile_state_test_cancel(state)) break; 149 150 // confirm regular files and directories in pre-order 151 if (cb_confirm && current_file->fts_info != FTS_DP) { 152 res = cb_confirm(state, 153 current_file->fts_path, state->confirm_context); 154 } 155 156 // don't process the file if a cancel has been requested 157 // by the callback 158 if (__removefile_state_test_cancel(state)) break; 159 160 if (res == REMOVEFILE_PROCEED) { 161 state->error_num = 0; 162 rval = __removefile_process_file(stream, current_file, 163 state); 164 165 if (state->error_num != 0) { 166 // Since removefile(3) is abstracting the 167 // traversal of the directory tree, suppress 168 // all ENOENT and ENOTDIR errors from the 169 // calling process. 170 // However, these errors should still be 171 // reported for the ROOTLEVEL since those paths 172 // were explicitly provided by the application. 173 if ((state->error_num != ENOENT && 174 state->error_num != ENOTDIR) || 175 current_file->fts_level == FTS_ROOTLEVEL) { 176 if (cb_error) { 177 res = cb_error(state, 178 current_file->fts_path, 179 state->error_context); 180 if (res == REMOVEFILE_PROCEED || 181 res == REMOVEFILE_SKIP) { 182 rval = 0; 183 } else if (res == REMOVEFILE_STOP) { 184 rval = -1; 185 } 186 } else { 187 res = REMOVEFILE_STOP; 188 } 189 } 190 // show status for regular files and directories 191 // in post-order 192 } else if (cb_status && 193 current_file->fts_info != FTS_D) { 194 res = cb_status(state, current_file->fts_path, 195 state->status_context); 196 } 197 } 198 199 if (current_file->fts_info == FTS_D && res == REMOVEFILE_SKIP) { 200 current_file->fts_number = REMOVEFILE_SKIP; 201 } 202 203 if (res == REMOVEFILE_SKIP || 204 !(state->unlink_flags & REMOVEFILE_RECURSIVE)) 205 fts_set(stream, current_file, FTS_SKIP); 206 if (res == REMOVEFILE_STOP || 207 __removefile_state_test_cancel(state)) 208 break; 209 } 210 211 if (__removefile_state_test_cancel(state)) { 212 errno = ECANCELED; 213 rval = -1; 214 } 215 216 fts_close(stream); 217 return rval; 218} 219