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/wait.h> 21#include <sys/stat.h> 22#include <errno.h> 23#include <limits.h> 24#include <signal.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <unistd.h> 29#include <libgen.h> 30 31#include <atalk/ftw.h> 32#include <atalk/adouble.h> 33#include <atalk/vfs.h> 34#include <atalk/util.h> 35#include <atalk/unix.h> 36#include <atalk/volume.h> 37#include <atalk/volinfo.h> 38#include <atalk/bstrlib.h> 39#include <atalk/bstradd.h> 40#include <atalk/queue.h> 41 42#include "ad.h" 43 44#define STRIP_TRAILING_SLASH(p) { \ 45 while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ 46 *--(p).p_end = 0; \ 47 } 48 49static int fflg, iflg, nflg, vflg; 50 51static afpvol_t svolume, dvolume; 52static cnid_t did, pdid; 53static volatile sig_atomic_t alarmed; 54static char *netatalk_dirs[] = { 55 ".AppleDouble", 56 ".AppleDB", 57 ".AppleDesktop", 58 NULL 59}; 60 61static int copy(const char *, const char *); 62static int do_move(const char *, const char *); 63static void preserve_fd_acls(int source_fd, int dest_fd, const char *source_path, 64 const char *dest_path); 65/* 66 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" 67 Returns pointer to name or NULL. 68*/ 69static const char *check_netatalk_dirs(const char *name) 70{ 71 int c; 72 73 for (c=0; netatalk_dirs[c]; c++) { 74 if ((strcmp(name, netatalk_dirs[c])) == 0) 75 return netatalk_dirs[c]; 76 } 77 return NULL; 78} 79 80/* 81 SIGNAL handling: 82 catch SIGINT and SIGTERM which cause clean exit. Ignore anything else. 83*/ 84 85static void sig_handler(int signo) 86{ 87 alarmed = 1; 88 return; 89} 90 91static void set_signal(void) 92{ 93 struct sigaction sv; 94 95 sv.sa_handler = sig_handler; 96 sv.sa_flags = SA_RESTART; 97 sigemptyset(&sv.sa_mask); 98 if (sigaction(SIGTERM, &sv, NULL) < 0) 99 ERROR("error in sigaction(SIGTERM): %s", strerror(errno)); 100 101 if (sigaction(SIGINT, &sv, NULL) < 0) 102 ERROR("error in sigaction(SIGINT): %s", strerror(errno)); 103 104 memset(&sv, 0, sizeof(struct sigaction)); 105 sv.sa_handler = SIG_IGN; 106 sigemptyset(&sv.sa_mask); 107 108 if (sigaction(SIGABRT, &sv, NULL) < 0) 109 ERROR("error in sigaction(SIGABRT): %s", strerror(errno)); 110 111 if (sigaction(SIGHUP, &sv, NULL) < 0) 112 ERROR("error in sigaction(SIGHUP): %s", strerror(errno)); 113 114 if (sigaction(SIGQUIT, &sv, NULL) < 0) 115 ERROR("error in sigaction(SIGQUIT): %s", strerror(errno)); 116} 117 118static void usage_mv(void) 119{ 120 printf( 121 "Usage: ad mv [-f | -i | -n] [-v] source target\n" 122 " ad mv [-f | -i | -n] [-v] source ... directory\n\n" 123 "Move files around within an AFP volume, updating the CNID\n" 124 "database as needed. If either:\n" 125 " - source or destination is not an AFP volume\n" 126 " - source volume != destinatio volume\n" 127 "the files are copied and removed from the source.\n\n" 128 "The following options are available:\n\n" 129 " -f Do not prompt for confirmation before overwriting the destination\n" 130 " path. (The -f option overrides any previous -i or -n options.)\n" 131 " -i Cause mv to write a prompt to standard error before moving a file\n" 132 " that would overwrite an existing file. If the response from the\n" 133 " standard input begins with the character `y' or `Y', the move is\n" 134 " attempted. (The -i option overrides any previous -f or -n\n" 135 " options.)\n" 136 " -n Do not overwrite an existing file. (The -n option overrides any\n" 137 " previous -f or -i options.)\n" 138 " -v Cause mv to be verbose, showing files after they are moved.\n" 139 ); 140 exit(EXIT_FAILURE); 141} 142 143int ad_mv(int argc, char *argv[]) 144{ 145 size_t baselen, len; 146 int rval; 147 char *p, *endp; 148 struct stat sb; 149 int ch; 150 char path[MAXPATHLEN]; 151 152 153 pdid = htonl(1); 154 did = htonl(2); 155 156 argc--; 157 argv++; 158 159 while ((ch = getopt(argc, argv, "finv")) != -1) 160 switch (ch) { 161 case 'i': 162 iflg = 1; 163 fflg = nflg = 0; 164 break; 165 case 'f': 166 fflg = 1; 167 iflg = nflg = 0; 168 break; 169 case 'n': 170 nflg = 1; 171 fflg = iflg = 0; 172 break; 173 case 'v': 174 vflg = 1; 175 break; 176 default: 177 usage_mv(); 178 } 179 180 argc -= optind; 181 argv += optind; 182 183 if (argc < 2) 184 usage_mv(); 185 186 set_signal(); 187 cnid_init(); 188 if (openvol(argv[argc - 1], &dvolume) != 0) { 189 SLOG("Error opening CNID database for source \"%s\": ", argv[argc - 1]); 190 return 1; 191 } 192 193 /* 194 * If the stat on the target fails or the target isn't a directory, 195 * try the move. More than 2 arguments is an error in this case. 196 */ 197 if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 198 if (argc > 2) 199 usage_mv(); 200 if (openvol(argv[0], &svolume) != 0) { 201 SLOG("Error opening CNID database for destination \"%s\": ", argv[0]); 202 return 1; 203 } 204 rval = do_move(argv[0], argv[1]); 205 closevol(&svolume); 206 closevol(&dvolume); 207 return 1; 208 } 209 210 /* It's a directory, move each file into it. */ 211 if (strlen(argv[argc - 1]) > sizeof(path) - 1) 212 ERROR("%s: destination pathname too long", *argv); 213 214 (void)strcpy(path, argv[argc - 1]); 215 baselen = strlen(path); 216 endp = &path[baselen]; 217 if (!baselen || *(endp - 1) != '/') { 218 *endp++ = '/'; 219 ++baselen; 220 } 221 222 for (rval = 0; --argc; ++argv) { 223 /* 224 * Find the last component of the source pathname. It 225 * may have trailing slashes. 226 */ 227 p = *argv + strlen(*argv); 228 while (p != *argv && p[-1] == '/') 229 --p; 230 while (p != *argv && p[-1] != '/') 231 --p; 232 233 if ((baselen + (len = strlen(p))) >= PATH_MAX) { 234 SLOG("%s: destination pathname too long", *argv); 235 rval = 1; 236 } else { 237 memmove(endp, p, (size_t)len + 1); 238 openvol(*argv, &svolume); 239 240 if (do_move(*argv, path)) 241 rval = 1; 242 closevol(&svolume); 243 } 244 } 245 246exit: 247 closevol(&dvolume); 248 return rval; 249} 250 251static int do_move(const char *from, const char *to) 252{ 253 struct stat sb; 254 int ask, ch, first; 255 256 /* 257 * Check access. If interactive and file exists, ask user if it 258 * should be replaced. Otherwise if file exists but isn't writable 259 * make sure the user wants to clobber it. 260 */ 261 if (!fflg && !access(to, F_OK)) { 262 263 /* prompt only if source exist */ 264 if (lstat(from, &sb) == -1) { 265 SLOG("%s: %s", from, strerror(errno)); 266 return (1); 267 } 268 269 ask = 0; 270 if (nflg) { 271 if (vflg) 272 printf("%s not overwritten\n", to); 273 return (0); 274 } else if (iflg) { 275 (void)fprintf(stderr, "overwrite %s? (y/n [n]) ", to); 276 ask = 1; 277 } else if (access(to, W_OK) && !stat(to, &sb)) { 278 (void)fprintf(stderr, "override for %s? (y/n [n]) ", to); 279 ask = 1; 280 } 281 if (ask) { 282 first = ch = getchar(); 283 while (ch != '\n' && ch != EOF) 284 ch = getchar(); 285 if (first != 'y' && first != 'Y') { 286 (void)fprintf(stderr, "not overwritten\n"); 287 return (0); 288 } 289 } 290 } 291 292 int mustcopy = 0; 293 /* 294 * Besides the usual EXDEV we copy instead of moving if 295 * 1) source AFP volume != dest AFP volume 296 * 2) either source or dest isn't even an AFP volume 297 */ 298 if (!svolume.volinfo.v_path 299 || !dvolume.volinfo.v_path 300 || strcmp(svolume.volinfo.v_path, dvolume.volinfo.v_path) != 0) 301 mustcopy = 1; 302 303 cnid_t cnid = 0; 304 if (!mustcopy) { 305 if ((cnid = cnid_for_path(&svolume, from, &did)) == CNID_INVALID) { 306 SLOG("Couldn't resolve CNID for %s", from); 307 return -1; 308 } 309 310 if (stat(from, &sb) != 0) { 311 SLOG("Cant stat %s: %s", to, strerror(errno)); 312 return -1; 313 } 314 315 if (rename(from, to) != 0) { 316 if (errno == EXDEV) { 317 mustcopy = 1; 318 char path[MAXPATHLEN]; 319 320 /* 321 * If the source is a symbolic link and is on another 322 * filesystem, it can be recreated at the destination. 323 */ 324 if (lstat(from, &sb) == -1) { 325 SLOG("%s: %s", from, strerror(errno)); 326 return (-1); 327 } 328 if (!S_ISLNK(sb.st_mode)) { 329 /* Can't mv(1) a mount point. */ 330 if (realpath(from, path) == NULL) { 331 SLOG("cannot resolve %s: %s: %s", from, path, strerror(errno)); 332 return (1); 333 } 334 } 335 } else { /* != EXDEV */ 336 SLOG("rename %s to %s: %s", from, to, strerror(errno)); 337 return (1); 338 } 339 } /* rename != 0*/ 340 341 switch (sb.st_mode & S_IFMT) { 342 case S_IFREG: 343 if (dvolume.volume.vfs->vfs_renamefile(&dvolume.volume, -1, from, to) != 0) { 344 SLOG("Error moving adouble file for %s", from); 345 return -1; 346 } 347 break; 348 case S_IFDIR: 349 break; 350 default: 351 SLOG("Not a file or dir: %s", from); 352 return -1; 353 } 354 355 /* get CNID of new parent dir */ 356 cnid_t newpdid, newdid; 357 if ((newdid = cnid_for_paths_parent(&dvolume, to, &newpdid)) == CNID_INVALID) { 358 SLOG("Couldn't resolve CNID for parent of %s", to); 359 return -1; 360 } 361 362 if (stat(to, &sb) != 0) { 363 SLOG("Cant stat %s: %s", to, strerror(errno)); 364 return 1; 365 } 366 367 char *p = strdup(to); 368 char *name = basename(p); 369 if (cnid_update(dvolume.volume.v_cdb, cnid, &sb, newdid, name, strlen(name)) != 0) { 370 SLOG("Cant update CNID for: %s", to); 371 return 1; 372 } 373 free(p); 374 375 struct adouble ad; 376 ad_init(&ad, dvolume.volinfo.v_adouble, dvolume.volinfo.v_ad_options); 377 if (ad_open_metadata(to, S_ISDIR(sb.st_mode) ? ADFLAGS_DIR : 0, O_RDWR, &ad) != 0) { 378 SLOG("Error opening adouble for: %s", to); 379 return 1; 380 } 381 ad_setid(&ad, sb.st_dev, sb.st_ino, cnid, newdid, dvolume.db_stamp); 382 ad_flush(&ad); 383 ad_close_metadata(&ad); 384 385 if (vflg) 386 printf("%s -> %s\n", from, to); 387 return (0); 388 } 389 390 if (mustcopy) 391 return copy(from, to); 392 393 /* If we get here it's an error */ 394 return -1; 395} 396 397static int copy(const char *from, const char *to) 398{ 399 struct stat sb; 400 int pid, status; 401 402 if (lstat(to, &sb) == 0) { 403 /* Destination path exists. */ 404 if (S_ISDIR(sb.st_mode)) { 405 if (rmdir(to) != 0) { 406 SLOG("rmdir %s: %s", to, strerror(errno)); 407 return (1); 408 } 409 } else { 410 if (unlink(to) != 0) { 411 SLOG("unlink %s: %s", to, strerror(errno)); 412 return (1); 413 } 414 } 415 } else if (errno != ENOENT) { 416 SLOG("%s: %s", to, strerror(errno)); 417 return (1); 418 } 419 420 /* Copy source to destination. */ 421 if (!(pid = fork())) { 422 execl(_PATH_AD, "ad", "cp", vflg ? "-Rpv" : "-Rp", from, to, (char *)NULL); 423 _exit(1); 424 } 425 while ((waitpid(pid, &status, 0)) == -1) { 426 if (errno == EINTR) 427 continue; 428 SLOG("%s cp -R %s %s: waitpid: %s", _PATH_AD, from, to, strerror(errno)); 429 return (1); 430 } 431 if (!WIFEXITED(status)) { 432 SLOG("%s cp -R %s %s: did not terminate normally", _PATH_AD, from, to); 433 return (1); 434 } 435 switch (WEXITSTATUS(status)) { 436 case 0: 437 break; 438 default: 439 SLOG("%s cp -R %s %s: terminated with %d (non-zero) status", 440 _PATH_AD, from, to, WEXITSTATUS(status)); 441 return (1); 442 } 443 444 /* Delete the source. */ 445 if (!(pid = fork())) { 446 execl(_PATH_AD, "ad", "rm", "-R", from, (char *)NULL); 447 _exit(1); 448 } 449 while ((waitpid(pid, &status, 0)) == -1) { 450 if (errno == EINTR) 451 continue; 452 SLOG("%s rm -R %s: waitpid: %s", _PATH_AD, from, strerror(errno)); 453 return (1); 454 } 455 if (!WIFEXITED(status)) { 456 SLOG("%s rm -R %s: did not terminate normally", _PATH_AD, from); 457 return (1); 458 } 459 switch (WEXITSTATUS(status)) { 460 case 0: 461 break; 462 default: 463 SLOG("%s rm -R %s: terminated with %d (non-zero) status", 464 _PATH_AD, from, WEXITSTATUS(status)); 465 return (1); 466 } 467 return 0; 468} 469 470static void 471preserve_fd_acls(int source_fd, 472 int dest_fd, 473 const char *source_path, 474 const char *dest_path) 475{ 476 ; 477} 478