1/* 2 * Copyright (c) 2010, Frank Lahm <franklahm@googlemail.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15#ifdef HAVE_CONFIG_H 16#include "config.h" 17#endif /* HAVE_CONFIG_H */ 18 19#include <sys/types.h> 20#include <sys/stat.h> 21#include <errno.h> 22#include <limits.h> 23#include <signal.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <unistd.h> 28 29#include <atalk/ftw.h> 30#include <atalk/adouble.h> 31#include <atalk/vfs.h> 32#include <atalk/util.h> 33#include <atalk/unix.h> 34#include <atalk/volume.h> 35#include <atalk/volinfo.h> 36#include <atalk/bstrlib.h> 37#include <atalk/bstradd.h> 38#include <atalk/queue.h> 39 40#include "ad.h" 41 42#define STRIP_TRAILING_SLASH(p) { \ 43 while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ 44 *--(p).p_end = 0; \ 45 } 46 47static afpvol_t volume; 48 49static cnid_t did, pdid; 50static int Rflag; 51static volatile sig_atomic_t alarmed; 52static int badrm, rval; 53 54static char *netatalk_dirs[] = { 55 ".AppleDB", 56 ".AppleDesktop", 57 NULL 58}; 59 60/* Forward declarations */ 61static int rm(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf); 62 63/* 64 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" 65 Returns pointer to name or NULL. 66*/ 67static const char *check_netatalk_dirs(const char *name) 68{ 69 int c; 70 71 for (c=0; netatalk_dirs[c]; c++) { 72 if ((strcmp(name, netatalk_dirs[c])) == 0) 73 return netatalk_dirs[c]; 74 } 75 return NULL; 76} 77 78static void upfunc(void) 79{ 80 did = pdid; 81} 82 83/* 84 SIGNAL handling: 85 catch SIGINT and SIGTERM which cause clean exit. Ignore anything else. 86*/ 87 88static void sig_handler(int signo) 89{ 90 alarmed = 1; 91 return; 92} 93 94static void set_signal(void) 95{ 96 struct sigaction sv; 97 98 sv.sa_handler = sig_handler; 99 sv.sa_flags = SA_RESTART; 100 sigemptyset(&sv.sa_mask); 101 if (sigaction(SIGTERM, &sv, NULL) < 0) 102 ERROR("error in sigaction(SIGTERM): %s", strerror(errno)); 103 104 if (sigaction(SIGINT, &sv, NULL) < 0) 105 ERROR("error in sigaction(SIGINT): %s", strerror(errno)); 106 107 memset(&sv, 0, sizeof(struct sigaction)); 108 sv.sa_handler = SIG_IGN; 109 sigemptyset(&sv.sa_mask); 110 111 if (sigaction(SIGABRT, &sv, NULL) < 0) 112 ERROR("error in sigaction(SIGABRT): %s", strerror(errno)); 113 114 if (sigaction(SIGHUP, &sv, NULL) < 0) 115 ERROR("error in sigaction(SIGHUP): %s", strerror(errno)); 116 117 if (sigaction(SIGQUIT, &sv, NULL) < 0) 118 ERROR("error in sigaction(SIGQUIT): %s", strerror(errno)); 119} 120 121static void usage_rm(void) 122{ 123 printf( 124 "Usage: ad rm [-vR] <file|dir> [<file|dir> ...]\n\n" 125 "The rm utility attempts to remove the non-directory type files specified\n" 126 "on the command line.\n" 127 "If the files and directories reside on an AFP volume, the corresponding\n" 128 "CNIDs are deleted from the volumes database.\n\n" 129 "The options are as follows:\n\n" 130 " -R Attempt to remove the file hierarchy rooted in each file argument.\n" 131 " -v Be verbose when deleting files, showing them as they are removed.\n" 132 ); 133 exit(EXIT_FAILURE); 134} 135 136int ad_rm(int argc, char *argv[]) 137{ 138 int ch; 139 140 pdid = htonl(1); 141 did = htonl(2); 142 143 while ((ch = getopt(argc, argv, "vR")) != -1) 144 switch (ch) { 145 case 'R': 146 Rflag = 1; 147 break; 148 case 'v': 149 vflag = 1; 150 break; 151 default: 152 usage_rm(); 153 break; 154 } 155 argc -= optind; 156 argv += optind; 157 158 if (argc < 1) 159 usage_rm(); 160 161 set_signal(); 162 cnid_init(); 163 164 /* Set end of argument list */ 165 argv[argc] = NULL; 166 167 for (int i = 0; argv[i] != NULL; i++) { 168 /* Load .volinfo file for source */ 169 openvol(argv[i], &volume); 170 171 if (nftw(argv[i], rm, upfunc, 20, FTW_DEPTH | FTW_PHYS) == -1) { 172 if (alarmed) { 173 SLOG("...break"); 174 } else { 175 SLOG("Error: %s", argv[i]); 176 } 177 closevol(&volume); 178 } 179 } 180 return rval; 181} 182 183static int rm(const char *path, 184 const struct stat *statp, 185 int tflag, 186 struct FTW *ftw) 187{ 188 cnid_t cnid; 189 190 if (alarmed) 191 return -1; 192 193 const char *dir = strrchr(path, '/'); 194 if (dir == NULL) 195 dir = path; 196 else 197 dir++; 198 if (check_netatalk_dirs(dir) != NULL) 199 return FTW_SKIP_SUBTREE; 200 201 switch (statp->st_mode & S_IFMT) { 202 203 case S_IFLNK: 204 if (volume.volinfo.v_path) { 205 if ((volume.volinfo.v_adouble == AD_VERSION2) 206 && (strstr(path, ".AppleDouble") != NULL)) { 207 /* symlink inside adouble dir */ 208 if (unlink(path) != 0) 209 badrm = rval = 1; 210 break; 211 } 212 213 /* Get CNID of Parent and add new childir to CNID database */ 214 pdid = did; 215 if ((cnid = cnid_for_path(&volume, path, &did)) == CNID_INVALID) { 216 SLOG("Error resolving CNID for %s", path); 217 return -1; 218 } 219 if (cnid_delete(volume.volume.v_cdb, cnid) != 0) { 220 SLOG("Error removing CNID %u for %s", ntohl(cnid), path); 221 return -1; 222 } 223 } 224 225 if (unlink(path) != 0) { 226 badrm = rval = 1; 227 break; 228 } 229 230 break; 231 232 case S_IFDIR: 233 if (!Rflag) { 234 SLOG("%s is a directory", path); 235 return FTW_SKIP_SUBTREE; 236 } 237 238 if (volume.volinfo.v_path) { 239 if ((volume.volinfo.v_adouble == AD_VERSION2) 240 && (strstr(path, ".AppleDouble") != NULL)) { 241 /* should be adouble dir itself */ 242 if (rmdir(path) != 0) { 243 SLOG("Error removing dir \"%s\": %s", path, strerror(errno)); 244 badrm = rval = 1; 245 return -1; 246 } 247 break; 248 } 249 250 /* Get CNID of Parent and add new childir to CNID database */ 251 if ((did = cnid_for_path(&volume, path, &pdid)) == CNID_INVALID) { 252 SLOG("Error resolving CNID for %s", path); 253 return -1; 254 } 255 if (cnid_delete(volume.volume.v_cdb, did) != 0) { 256 SLOG("Error removing CNID %u for %s", ntohl(did), path); 257 return -1; 258 } 259 } 260 261 if (rmdir(path) != 0) { 262 SLOG("Error removing dir \"%s\": %s", path, strerror(errno)); 263 badrm = rval = 1; 264 return -1; 265 } 266 267 break; 268 269 case S_IFBLK: 270 case S_IFCHR: 271 SLOG("%s is a device file.", path); 272 badrm = rval = 1; 273 break; 274 275 case S_IFSOCK: 276 SLOG("%s is a socket.", path); 277 badrm = rval = 1; 278 break; 279 280 case S_IFIFO: 281 SLOG("%s is a FIFO.", path); 282 badrm = rval = 1; 283 break; 284 285 default: 286 if (volume.volinfo.v_path) { 287 if ((volume.volinfo.v_adouble == AD_VERSION2) 288 && (strstr(path, ".AppleDouble") != NULL)) { 289 /* file in adouble dir */ 290 if (unlink(path) != 0) 291 badrm = rval = 1; 292 break; 293 } 294 295 /* Get CNID of Parent and add new childir to CNID database */ 296 pdid = did; 297 if ((cnid = cnid_for_path(&volume, path, &did)) == CNID_INVALID) { 298 SLOG("Error resolving CNID for %s", path); 299 return -1; 300 } 301 if (cnid_delete(volume.volume.v_cdb, cnid) != 0) { 302 SLOG("Error removing CNID %u for %s", ntohl(cnid), path); 303 return -1; 304 } 305 306 /* Ignore errors, because with -R adouble stuff is always alread gone */ 307 volume.volume.vfs->vfs_deletefile(&volume.volume, -1, path); 308 } 309 310 if (unlink(path) != 0) { 311 badrm = rval = 1; 312 break; 313 } 314 315 break; 316 } 317 318 if (vflag && !badrm) 319 (void)printf("%s\n", path); 320 321 return 0; 322} 323