]> arthur.barton.de Git - netatalk.git/blob - bin/ad/ad_mv.c
ad mv nearly done, only updating adouble file missing
[netatalk.git] / bin / ad / ad_mv.c
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
49 static int fflg, iflg, nflg, vflg;
50
51 static afpvol_t svolume, dvolume;
52 static cnid_t did, pdid;
53 static volatile sig_atomic_t alarmed;
54 static char *adexecp;
55 static char           *netatalk_dirs[] = {
56     ".AppleDouble",
57     ".AppleDB",
58     ".AppleDesktop",
59     NULL
60 };
61
62 static int copy(const char *, const char *);
63 static int do_move(const char *, const char *);
64 static int fastcopy(const char *, const char *, struct stat *);
65 static void preserve_fd_acls(int source_fd, int dest_fd, const char *source_path,
66                              const char *dest_path);
67 /*
68   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
69   Returns pointer to name or NULL.
70 */
71 static const char *check_netatalk_dirs(const char *name)
72 {
73     int c;
74
75     for (c=0; netatalk_dirs[c]; c++) {
76         if ((strcmp(name, netatalk_dirs[c])) == 0)
77             return netatalk_dirs[c];
78     }
79     return NULL;
80 }
81
82 /*
83   SIGNAL handling:
84   catch SIGINT and SIGTERM which cause clean exit. Ignore anything else.
85 */
86
87 static void sig_handler(int signo)
88 {
89     alarmed = 1;
90     return;
91 }
92
93 static void set_signal(void)
94 {
95     struct sigaction sv;
96
97     sv.sa_handler = sig_handler;
98     sv.sa_flags = SA_RESTART;
99     sigemptyset(&sv.sa_mask);
100     if (sigaction(SIGTERM, &sv, NULL) < 0)
101         ERROR("error in sigaction(SIGTERM): %s", strerror(errno));
102
103     if (sigaction(SIGINT, &sv, NULL) < 0)
104         ERROR("error in sigaction(SIGINT): %s", strerror(errno));
105
106     memset(&sv, 0, sizeof(struct sigaction));
107     sv.sa_handler = SIG_IGN;
108     sigemptyset(&sv.sa_mask);
109
110     if (sigaction(SIGABRT, &sv, NULL) < 0)
111         ERROR("error in sigaction(SIGABRT): %s", strerror(errno));
112
113     if (sigaction(SIGHUP, &sv, NULL) < 0)
114         ERROR("error in sigaction(SIGHUP): %s", strerror(errno));
115
116     if (sigaction(SIGQUIT, &sv, NULL) < 0)
117         ERROR("error in sigaction(SIGQUIT): %s", strerror(errno));
118 }
119
120 static void usage_mv(void)
121 {
122     printf(
123         "Usage: ad mv [-f | -i | -n] [-v] source target\n"
124         "       ad mv [-f | -i | -n] [-v] source ... directory\n"
125         );
126     exit(EXIT_FAILURE);
127 }
128
129 int ad_mv(int argc, char *argv[])
130 {
131     size_t baselen, len;
132     int rval;
133     char *p, *endp;
134     struct stat sb;
135     int ch;
136     char path[MAXPATHLEN];
137
138
139     pdid = htonl(1);
140     did = htonl(2);
141
142     adexecp = argv[0];
143     argc--;
144     argv++;
145
146     while ((ch = getopt(argc, argv, "finv")) != -1)
147         switch (ch) {
148         case 'i':
149             iflg = 1;
150             fflg = nflg = 0;
151             break;
152         case 'f':
153             fflg = 1;
154             iflg = nflg = 0;
155             break;
156         case 'n':
157             nflg = 1;
158             fflg = iflg = 0;
159             break;
160         case 'v':
161             vflg = 1;
162             break;
163         default:
164             usage_mv();
165         }
166
167     argc -= optind;
168     argv += optind;
169
170     if (argc < 2)
171         usage_mv();
172
173     set_signal();
174     cnid_init();
175     if (openvol(argv[argc - 1], &dvolume) != 0) {
176         SLOG("Error opening CNID database for %s: ", argv[argc - 1]);
177         return 1;
178     }
179
180     /*
181      * If the stat on the target fails or the target isn't a directory,
182      * try the move.  More than 2 arguments is an error in this case.
183      */
184     if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
185         if (argc > 2)
186             usage_mv();
187         if (openvol(argv[0], &svolume) != 0) {
188             SLOG("Error opening CNID database for %s: ", argv[0]);
189             return 1;
190         }
191         rval = do_move(argv[0], argv[1]);
192         closevol(&svolume);
193         closevol(&dvolume);
194     }
195
196     /* It's a directory, move each file into it. */
197     if (strlen(argv[argc - 1]) > sizeof(path) - 1)
198         ERROR("%s: destination pathname too long", *argv);
199
200     (void)strcpy(path, argv[argc - 1]);
201     baselen = strlen(path);
202     endp = &path[baselen];
203     if (!baselen || *(endp - 1) != '/') {
204         *endp++ = '/';
205         ++baselen;
206     }
207
208     for (rval = 0; --argc; ++argv) {
209         /*
210          * Find the last component of the source pathname.  It
211          * may have trailing slashes.
212          */
213         p = *argv + strlen(*argv);
214         while (p != *argv && p[-1] == '/')
215             --p;
216         while (p != *argv && p[-1] != '/')
217             --p;
218
219         if ((baselen + (len = strlen(p))) >= PATH_MAX) {
220             SLOG("%s: destination pathname too long", *argv);
221             rval = 1;
222         } else {
223             memmove(endp, p, (size_t)len + 1);
224             if (openvol(*argv, &svolume) != 0) {
225                 SLOG("Error opening CNID database for %s: ", argv[0]);
226                 rval = 1;
227                 goto exit;
228             }
229             if (do_move(*argv, path))
230                 rval = 1;
231             closevol(&svolume);
232         }
233     }
234
235 exit:
236     closevol(&dvolume);
237     return rval;
238 }
239
240 static int do_move(const char *from, const char *to)
241 {
242     struct stat sb;
243     int ask, ch, first;
244
245     /*
246      * Check access.  If interactive and file exists, ask user if it
247      * should be replaced.  Otherwise if file exists but isn't writable
248      * make sure the user wants to clobber it.
249      */
250     if (!fflg && !access(to, F_OK)) {
251
252         /* prompt only if source exist */
253         if (lstat(from, &sb) == -1) {
254             SLOG("%s: %s", from, strerror(errno));
255             return (1);
256         }
257
258         ask = 0;
259         if (nflg) {
260             if (vflg)
261                 printf("%s not overwritten\n", to);
262             return (0);
263         } else if (iflg) {
264             (void)fprintf(stderr, "overwrite %s? (y/n [n]) ", to);
265             ask = 1;
266         } else if (access(to, W_OK) && !stat(to, &sb)) {
267             (void)fprintf(stderr, "override for %s? (y/n [n]) ", to);
268             ask = 1;
269         }
270         if (ask) {
271             first = ch = getchar();
272             while (ch != '\n' && ch != EOF)
273                 ch = getchar();
274             if (first != 'y' && first != 'Y') {
275                 (void)fprintf(stderr, "not overwritten\n");
276                 return (0);
277             }
278         }
279     }
280
281     int mustcopy = 0;
282     /* 
283      * Besides the usual EXDEV we copy instead of moving if
284      * 1) source AFP volume != dest AFP volume
285      * 2) either source or dest isn't even an AFP volume
286      */
287     if (!svolume.volinfo.v_path
288         || !dvolume.volinfo.v_path
289         || strcmp(svolume.volinfo.v_path, dvolume.volinfo.v_path) != 0)
290         mustcopy = 1;
291     
292     cnid_t cnid = 0;
293     if (!mustcopy) {
294         if ((cnid = cnid_for_path(&svolume, from, &did)) == CNID_INVALID) {
295             SLOG("Couldn't resolve CNID for %s", from);
296             return -1;
297         }
298
299         if (stat(from, &sb) != 0) {
300             SLOG("Cant stat %s: %s", to, strerror(errno));
301             return -1;
302         }
303
304         if (rename(from, to) != 0) {
305             if (errno == EXDEV) {
306                 mustcopy = 1;
307                 char path[MAXPATHLEN];
308
309                 /*
310                  * If the source is a symbolic link and is on another
311                  * filesystem, it can be recreated at the destination.
312                  */
313                 if (lstat(from, &sb) == -1) {
314                     SLOG("%s: %s", from, strerror(errno));
315                     return (-1);
316                 }
317                 if (!S_ISLNK(sb.st_mode)) {
318                     /* Can't mv(1) a mount point. */
319                     if (realpath(from, path) == NULL) {
320                         SLOG("cannot resolve %s: %s: %s", from, path, strerror(errno));
321                         return (1);
322                     }
323                 }
324             } else { /* != EXDEV */
325                 SLOG("rename %s to %s: %s", from, to, strerror(errno));
326                 return (1);
327             }
328         } /* rename != 0*/
329         
330         switch (sb.st_mode & S_IFMT) {
331         case S_IFREG:
332             if (dvolume.volume.vfs->vfs_renamefile(&dvolume.volume, -1, from, to) != 0) {
333                 SLOG("Error moving adouble file for %s", from);
334                 return -1;
335             }
336             break;
337         case S_IFDIR:
338             break;
339         default:
340             SLOG("Not a file or dir: %s", from);
341             return -1;
342         }
343     
344         char *newdir = strdup(to);
345         cnid_t newdid;
346         if ((newdid = cnid_for_path(&svolume, dirname(newdir), &pdid)) == CNID_INVALID) {
347             SLOG("Couldn't resolve CNID for %s", to);
348         }
349         free(newdir);
350
351         if (stat(to, &sb) != 0) {
352             SLOG("Cant stat %s: %s", to, strerror(errno));
353             return 1;
354         }
355         char *name = strdup(to);
356         if (cnid_update(dvolume.volume.v_cdb, cnid, &sb, newdid, basename(to), strlen(basename(to))) != 0) {
357             SLOG("Cant update CNID for: %s", to);
358             return 1;
359         }
360         free(name);
361
362         if (vflg)
363             printf("%s -> %s\n", from, to);
364         return (0);
365     }
366
367     if (mustcopy) {
368         /*
369          * If rename fails because we're trying to cross devices, and
370          * it's a regular file, do the copy internally; otherwise, use
371          * cp and rm.
372          */
373         if (lstat(from, &sb)) {
374             SLOG("%s: %s", from, strerror(errno));
375             return (1);
376         }
377         return (S_ISREG(sb.st_mode) ?
378                 fastcopy(from, to, &sb) : copy(from, to));
379     }
380     return 1;
381 }
382
383 static int fastcopy(const char *from, const char *to, struct stat *sbp)
384 {
385     struct timeval tval[2];
386     static u_int blen;
387     static char *bp;
388     mode_t oldmode;
389     int nread, from_fd, to_fd;
390
391     if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
392         SLOG("%s: %s", from, strerror(errno));
393         return (1);
394     }
395     if (blen < sbp->st_blksize) {
396         if (bp != NULL)
397             free(bp);
398         if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
399             blen = 0;
400             SLOG("malloc failed");
401             return (1);
402         }
403         blen = sbp->st_blksize;
404     }
405     while ((to_fd = open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
406         if (errno == EEXIST && unlink(to) == 0)
407             continue;
408         SLOG("%s: %s", to, strerror(errno));
409         (void)close(from_fd);
410         return (1);
411     }
412     while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
413         if (write(to_fd, bp, (size_t)nread) != nread) {
414             SLOG("%s: %s", to, strerror(errno));
415             goto err;
416         }
417     if (nread < 0) {
418         SLOG("%s: %s", from, strerror(errno));
419     err:
420         if (unlink(to))
421             SLOG("%s: remove", to, strerror(errno));
422         (void)close(from_fd);
423         (void)close(to_fd);
424         return (1);
425     }
426
427     oldmode = sbp->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID);
428     if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
429         SLOG("%s: set owner/group (was: %lu/%lu)", to,
430              (u_long)sbp->st_uid, (u_long)sbp->st_gid, strerror(errno));
431         if (oldmode & S_ISUID) {
432             SLOG("%s: owner/group changed; clearing suid (mode was 0%03o)",
433                  to, oldmode);
434             sbp->st_mode &= ~S_ISUID;
435         }
436     }
437     if (fchmod(to_fd, sbp->st_mode))
438         SLOG("%s: set mode (was: 0%03o): %s", to, oldmode, strerror(errno));
439     /*
440      * POSIX 1003.2c states that if _POSIX_ACL_EXTENDED is in effect
441      * for dest_file, then its ACLs shall reflect the ACLs of the
442      * source_file.
443      */
444     preserve_fd_acls(from_fd, to_fd, from, to);
445     (void)close(from_fd);
446
447     tval[0].tv_sec = sbp->st_atime;
448     tval[1].tv_sec = sbp->st_mtime;
449     tval[0].tv_usec = tval[1].tv_usec = 0;
450     if (utimes(to, tval))
451         SLOG("%s: set times: %s", to, strerror(errno));
452
453     if (close(to_fd)) {
454         SLOG("%s: %s", to, strerror(errno));
455         return (1);
456     }
457
458     if (unlink(from)) {
459         SLOG("%s: remove: %s", from, strerror(errno));
460         return (1);
461     }
462     if (vflg)
463         printf("%s -> %s\n", from, to);
464     return 0;
465 }
466
467 static int copy(const char *from, const char *to)
468 {
469     struct stat sb;
470     int pid, status;
471
472     if (lstat(to, &sb) == 0) {
473         /* Destination path exists. */
474         if (S_ISDIR(sb.st_mode)) {
475             if (rmdir(to) != 0) {
476                 SLOG("rmdir %s: %s", to, strerror(errno));
477                 return (1);
478             }
479         } else {
480             if (unlink(to) != 0) {
481                 SLOG("unlink %s: %s", to, strerror(errno));
482                 return (1);
483             }
484         }
485     } else if (errno != ENOENT) {
486         SLOG("%s: %s", to, strerror(errno));
487         return (1);
488     }
489
490     /* Copy source to destination. */
491     if (!(pid = fork())) {
492         execl(adexecp, "cp", vflg ? "-Rpv" : "-Rp", "--", from, to, (char *)NULL);
493         _exit(1);
494     }
495     if (waitpid(pid, &status, 0) == -1) {
496         SLOG("%s %s %s: waitpid: %s", adexecp, from, to, strerror(errno));
497         return (1);
498     }
499     if (!WIFEXITED(status)) {
500         SLOG("%s %s %s: did not terminate normally", adexecp, from, to);
501         return (1);
502     }
503     switch (WEXITSTATUS(status)) {
504     case 0:
505         break;
506     default:
507         SLOG("%s %s %s: terminated with %d (non-zero) status",
508               adexecp, from, to, WEXITSTATUS(status));
509         return (1);
510     }
511
512     /* Delete the source. */
513     if (!(pid = fork())) {
514         execl(adexecp, "rm", "-Rf", "--", from, (char *)NULL);
515         _exit(1);
516     }
517     if (waitpid(pid, &status, 0) == -1) {
518         SLOG("%s %s: waitpid: %s", adexecp, from, strerror(errno));
519         return (1);
520     }
521     if (!WIFEXITED(status)) {
522         SLOG("%s %s: did not terminate normally", adexecp, from);
523         return (1);
524     }
525     switch (WEXITSTATUS(status)) {
526     case 0:
527         break;
528     default:
529         SLOG("%s %s: terminated with %d (non-zero) status",
530               adexecp, from, WEXITSTATUS(status));
531         return (1);
532     }
533     return 0;
534 }
535
536 static void
537 preserve_fd_acls(int source_fd,
538                  int dest_fd,
539                  const char *source_path,
540                  const char *dest_path)
541 {
542     ;
543 }