]> arthur.barton.de Git - netatalk.git/blob - bin/ad/ad_cp.c
CNID for new dirs works with ad cp -R
[netatalk.git] / bin / ad / ad_cp.c
1 /*
2  * Copyright (c) 2010, Frank Lahm <franklahm@googlemail.com>
3  * Copyright (c) 1988, 1993, 1994
4  * The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * David Hitz of Auspex Systems Inc.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 /*
35  * Cp copies source files to target files.
36  *
37  * The global PATH_T structure "to" always contains the path to the
38  * current target file.  Since fts(3) does not change directories,
39  * this path can be either absolute or dot-relative.
40  *
41  * The basic algorithm is to initialize "to" and use fts(3) to traverse
42  * the file hierarchy rooted in the argument list.  A trivial case is the
43  * case of 'cp file1 file2'.  The more interesting case is the case of
44  * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
45  * path (relative to the root of the traversal) is appended to dir (stored
46  * in "to") to form the final target path.
47  */
48
49 #ifdef HAVE_CONFIG_H
50 #include "config.h"
51 #endif /* HAVE_CONFIG_H */
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <errno.h>
56 #include <limits.h>
57 #include <signal.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62
63 #include <atalk/ftw.h>
64 #include <atalk/adouble.h>
65 #include <atalk/vfs.h>
66 #include <atalk/util.h>
67 #include <atalk/unix.h>
68 #include <atalk/volume.h>
69 #include <atalk/volinfo.h>
70 #include <atalk/bstrlib.h>
71 #include <atalk/bstradd.h>
72 #include <atalk/queue.h>
73
74 #include "ad.h"
75
76 #define STRIP_TRAILING_SLASH(p) {                                   \
77         while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/')  \
78             *--(p).p_end = 0;                                       \
79     }
80
81 static char emptystring[] = "";
82
83 PATH_T to = { to.p_path, emptystring, "" };
84
85 int fflag, iflag, lflag, nflag, pflag, vflag;
86 mode_t mask;
87 struct volinfo svolinfo, dvolinfo;
88 struct vol svolume, dvolume;
89 cnid_t did = 0; /* current dir CNID */
90
91 static enum op type;
92 static int Rflag;
93 volatile sig_atomic_t sigint;
94 static int badcp, rval;
95 static int ftw_options = FTW_MOUNT | FTW_PHYS | FTW_ACTIONRETVAL;
96 static q_t *cnidq;              /* CNID dir stack */
97
98 enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
99
100 static char           *netatalk_dirs[] = {
101     ".AppleDouble",
102     ".AppleDB",
103     ".AppleDesktop",
104     NULL
105 };
106
107 static int copy(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf);
108 static void siginfo(int _U_);
109
110 /*
111   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
112   Returns pointer to name or NULL.
113 */
114 static const char *check_netatalk_dirs(const char *name)
115 {
116     int c;
117
118     for (c=0; netatalk_dirs[c]; c++) {
119         if ((strcmp(name, netatalk_dirs[c])) == 0)
120             return netatalk_dirs[c];
121     }
122     return NULL;
123 }
124
125
126 static void usage_cp(void)
127 {
128     printf(
129         "Usage: ad cp [-R [-P]] [-pvf] <source_file> <target_file>\n"
130         "Usage: ad cp [-R [-P]] [-pvfx] <source_file [source_file ...]> <target_directory>\n"
131         );
132     exit(EXIT_FAILURE);
133 }
134
135 static void upfunc(void)
136 {
137     if (cnidq) {
138         cnid_t *cnid = dequeue(cnidq);
139         if (cnid) {
140             did = *cnid;
141             free(cnid);
142         }
143     }
144 }
145
146 int ad_cp(int argc, char *argv[])
147 {
148     struct stat to_stat, tmp_stat;
149     int r, ch, have_trailing_slash;
150     char *target;
151 #if 0
152     afpvol_t srcvol;
153     afpvol_t dstvol;
154 #endif
155
156     while ((ch = getopt(argc, argv, "Rafilnpvx")) != -1)
157         switch (ch) {
158         case 'R':
159             Rflag = 1;
160             break;
161         case 'a':
162             pflag = 1;
163             Rflag = 1;
164             break;
165         case 'f':
166             fflag = 1;
167             iflag = nflag = 0;
168             break;
169         case 'i':
170             iflag = 1;
171             fflag = nflag = 0;
172             break;
173         case 'l':
174             lflag = 1;
175             break;
176         case 'n':
177             nflag = 1;
178             fflag = iflag = 0;
179             break;
180         case 'p':
181             pflag = 1;
182             break;
183         case 'v':
184             vflag = 1;
185             break;
186         case 'x':
187             ftw_options |= FTW_MOUNT;
188             break;
189         default:
190             usage_cp();
191             break;
192         }
193     argc -= optind;
194     argv += optind;
195
196     if (argc < 2)
197         usage_cp();
198
199     (void)signal(SIGINT, siginfo);
200
201     cnid_init();
202
203     /* Save the target base in "to". */
204     target = argv[--argc];
205     if ((strlcpy(to.p_path, target, PATH_MAX)) >= PATH_MAX)
206         ERROR("%s: name too long", target);
207
208     to.p_end = to.p_path + strlen(to.p_path);
209     if (to.p_path == to.p_end) {
210         *to.p_end++ = '.';
211         *to.p_end = 0;
212     }
213     have_trailing_slash = (to.p_end[-1] == '/');
214     if (have_trailing_slash)
215         STRIP_TRAILING_SLASH(to);
216     to.target_end = to.p_end;
217
218     /* Set end of argument list */
219     argv[argc] = NULL;
220
221     /*
222      * Cp has two distinct cases:
223      *
224      * cp [-R] source target
225      * cp [-R] source1 ... sourceN directory
226      *
227      * In both cases, source can be either a file or a directory.
228      *
229      * In (1), the target becomes a copy of the source. That is, if the
230      * source is a file, the target will be a file, and likewise for
231      * directories.
232      *
233      * In (2), the real target is not directory, but "directory/source".
234      */
235     r = stat(to.p_path, &to_stat);
236     if (r == -1 && errno != ENOENT)
237         ERROR("%s", to.p_path);
238     if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
239         /*
240          * Case (1).  Target is not a directory.
241          */
242         if (argc > 1)
243             ERROR("%s is not a directory", to.p_path);
244
245         /*
246          * Need to detect the case:
247          *cp -R dir foo
248          * Where dir is a directory and foo does not exist, where
249          * we want pathname concatenations turned on but not for
250          * the initial mkdir().
251          */
252         if (r == -1) {
253             lstat(*argv, &tmp_stat);
254
255             if (S_ISDIR(tmp_stat.st_mode) && Rflag)
256                 type = DIR_TO_DNE;
257             else
258                 type = FILE_TO_FILE;
259         } else
260             type = FILE_TO_FILE;
261
262         if (have_trailing_slash && type == FILE_TO_FILE) {
263             if (r == -1)
264                 ERROR("directory %s does not exist", to.p_path);
265             else
266                 ERROR("%s is not a directory", to.p_path);
267         }
268     } else
269         /*
270          * Case (2).  Target is a directory.
271          */
272         type = FILE_TO_DIR;
273
274     /*
275      * Keep an inverted copy of the umask, for use in correcting
276      * permissions on created directories when not using -p.
277      */
278     mask = ~umask(0777);
279     umask(~mask);
280
281 #if 0
282     /* Inhereting perms in ad_mkdir etc requires this */
283     ad_setfuid(0);
284 #endif
285
286     /* Load .volinfo file for destination*/
287     if (loadvolinfo(to.p_path, &dvolinfo) == 0) {
288         if (vol_load_charsets(&dvolinfo) == -1)
289             ERROR("Error loading charsets!");
290         /* Sanity checks to ensure we can touch this volume */
291         if (dvolinfo.v_vfs_ea != AFPVOL_EA_SYS)
292             ERROR("Unsupported Extended Attributes option: %u", dvolinfo.v_vfs_ea);
293
294         /* initialize sufficient struct vol and initialize CNID connection */
295         dvolume.v_adouble = AD_VERSION2;
296         dvolume.v_vfs_ea = AFPVOL_EA_SYS;
297         initvol_vfs(&dvolume);
298         int flags = 0;
299         if ((dvolinfo.v_flags & AFPVOL_NODEV))
300             flags |= CNID_FLAG_NODEV;
301
302         if ((dvolume.v_cdb = cnid_open(dvolinfo.v_dbpath,
303                                        0000,
304                                        "dbd",
305                                        flags,
306                                        dvolinfo.v_dbd_host,
307                                        dvolinfo.v_dbd_port)) == NULL)
308             ERROR("Cant initialize CNID database connection for %s", dvolinfo.v_path);
309
310         /* setup a list for storing the CNID stack of dirs in destination path */
311         if ((cnidq = queue_init()) == NULL)
312             ERROR("Cant initialize CNID stack");
313     }
314
315     for (int i = 0; argv[i] != NULL; i++) { 
316         /* Load .volinfo file for source */
317         if (loadvolinfo(to.p_path, &svolinfo) == 0) {
318             if (vol_load_charsets(&svolinfo) == -1)
319                 ERROR("Error loading charsets!");
320             /* Sanity checks to ensure we can touch this volume */
321             if (svolinfo.v_vfs_ea != AFPVOL_EA_SYS)
322                 ERROR("Unsupported Extended Attributes option: %u", svolinfo.v_vfs_ea);
323
324             /* initialize sufficient struct vol and initialize CNID connection */
325             svolume.v_adouble = AD_VERSION2;
326             svolume.v_vfs_ea = AFPVOL_EA_SYS;
327             initvol_vfs(&svolume);
328             int flags = 0;
329             if ((svolinfo.v_flags & AFPVOL_NODEV))
330                 flags |= CNID_FLAG_NODEV;
331
332             if ((svolume.v_cdb = cnid_open(svolinfo.v_dbpath,
333                                            0000,
334                                            "dbd",
335                                            flags,
336                                            svolinfo.v_dbd_host,
337                                            svolinfo.v_dbd_port)) == NULL)
338                 ERROR("Cant initialize CNID database connection for %s", svolinfo.v_path);
339         }
340
341         if (nftw(argv[i], copy, upfunc, 20, ftw_options) == -1) {
342             ERROR("%s: %s", argv[i], strerror(errno));
343             exit(EXIT_FAILURE);
344         }
345
346         if (svolume.v_cdb)
347             cnid_close(svolume.v_cdb);
348         svolume.v_cdb = NULL;
349
350     }
351     return rval;
352 }
353
354 static int copy(const char *path,
355                 const struct stat *statp,
356                 int tflag,
357                 struct FTW *ftw)
358 {
359     struct stat to_stat;
360     int base = 0, dne;
361     size_t nlen;
362     const char *p;
363     char *target_mid;
364
365     const char *dir = strrchr(path, '/');
366     if (dir == NULL)
367         dir = path;
368     else
369         dir++;
370     if (check_netatalk_dirs(dir) != NULL) {
371         SLOG("Skipping Netatalk dir %s", path);
372         return FTW_SKIP_SIBLINGS;
373     }
374
375     /*
376      * If we are in case (2) above, we need to append the
377      * source name to the target name.
378      */
379     if (type != FILE_TO_FILE) {
380         /*
381          * Need to remember the roots of traversals to create
382          * correct pathnames.  If there's a directory being
383          * copied to a non-existent directory, e.g.
384          *     cp -R a/dir noexist
385          * the resulting path name should be noexist/foo, not
386          * noexist/dir/foo (where foo is a file in dir), which
387          * is the case where the target exists.
388          *
389          * Also, check for "..".  This is for correct path
390          * concatenation for paths ending in "..", e.g.
391          *     cp -R .. /tmp
392          * Paths ending in ".." are changed to ".".  This is
393          * tricky, but seems the easiest way to fix the problem.
394          *
395          * XXX
396          * Since the first level MUST be FTS_ROOTLEVEL, base
397          * is always initialized.
398          */
399         if (ftw->level == 0) {
400             if (type != DIR_TO_DNE) {
401                 base = ftw->base;
402
403                 if (strcmp(&path[base], "..") == 0)
404                     base += 1;
405             } else
406                 base = strlen(path);
407         }
408
409         p = &path[base];
410         nlen = strlen(path) - base;
411         target_mid = to.target_end;
412         if (*p != '/' && target_mid[-1] != '/')
413             *target_mid++ = '/';
414         *target_mid = 0;
415         if (target_mid - to.p_path + nlen >= PATH_MAX) {
416             SLOG("%s%s: name too long (not copied)", to.p_path, p);
417             badcp = rval = 1;
418             return 0;
419         }
420         (void)strncat(target_mid, p, nlen);
421         to.p_end = target_mid + nlen;
422         *to.p_end = 0;
423         STRIP_TRAILING_SLASH(to);
424     }
425
426     /* Not an error but need to remember it happened */
427     if (stat(to.p_path, &to_stat) == -1)
428         dne = 1;
429     else {
430         if (to_stat.st_dev == statp->st_dev &&
431             to_stat.st_ino == statp->st_ino) {
432             SLOG("%s and %s are identical (not copied).",
433                 to.p_path, path);
434             badcp = rval = 1;
435             if (S_ISDIR(statp->st_mode))
436                 /* without using glibc extension FTW_ACTIONRETVAL cant handle this */
437                 return -1;
438         }
439         if (!S_ISDIR(statp->st_mode) &&
440             S_ISDIR(to_stat.st_mode)) {
441             SLOG("cannot overwrite directory %s with "
442                 "non-directory %s",
443                 to.p_path, path);
444                 badcp = rval = 1;
445                 return 0;
446         }
447         dne = 0;
448     }
449
450     switch (statp->st_mode & S_IFMT) {
451     case S_IFLNK:
452         if (copy_link(ftw, path, statp, !dne))
453             badcp = rval = 1;
454         break;
455     case S_IFDIR:
456         if (!Rflag) {
457             SLOG("%s is a directory", path);
458             badcp = rval = 1;
459             return -1;
460         }
461         /*
462          * If the directory doesn't exist, create the new
463          * one with the from file mode plus owner RWX bits,
464          * modified by the umask.  Trade-off between being
465          * able to write the directory (if from directory is
466          * 555) and not causing a permissions race.  If the
467          * umask blocks owner writes, we fail..
468          */
469         if (dne) {
470             if (mkdir(to.p_path, statp->st_mode | S_IRWXU) < 0)
471                 ERROR("%s", to.p_path);
472         } else if (!S_ISDIR(to_stat.st_mode)) {
473             errno = ENOTDIR;
474             ERROR("%s", to.p_path);
475         }
476
477         /* Create ad dir and copy ".Parent" */
478         if (svolinfo.v_path && svolinfo.v_adouble == AD_VERSION2 &&
479             dvolinfo.v_path && dvolinfo.v_adouble == AD_VERSION2) {
480             /* Create ".AppleDouble" dir */
481             mode_t omask = umask(0);
482             bstring addir = bfromcstr(to.p_path);
483             bcatcstr(addir, "/.AppleDouble");
484             mkdir(cfrombstr(addir), 02777);
485
486             /* copy ".Parent" file */
487             bcatcstr(addir, "/.Parent");
488             bstring sdir = bfromcstr(path);
489             bcatcstr(sdir, "/.AppleDouble/.Parent");
490             if (copy_file(-1, cfrombstr(sdir), cfrombstr(addir), 0666) != 0) {
491                 SLOG("Error copying %s -> %s", cfrombstr(sdir), cfrombstr(addir));
492                 badcp = rval = 1;
493                 break;
494             }
495             umask(omask);
496
497             /* Get CNID of Parent and add new childir to CNID database */
498             did = cnid_for_path(&dvolinfo, &dvolume, to.p_path);
499             SLOG("got CNID: %u for path: %s", ntohl(did), to.p_path);
500         }
501
502         if (pflag) {
503             if (setfile(statp, -1))
504                 rval = 1;
505 #if 0
506             if (preserve_dir_acls(statp, curr->fts_accpath, to.p_path) != 0)
507                 rval = 1;
508 #endif
509         }
510         break;
511
512     case S_IFBLK:
513     case S_IFCHR:
514         SLOG("%s is a device file (not copied).", path);
515         break;
516     case S_IFSOCK:
517         SLOG("%s is a socket (not copied).", path);
518         break;
519     case S_IFIFO:
520         SLOG("%s is a FIFO (not copied).", path);
521         break;
522     default:
523         if (ftw_copy_file(ftw, path, statp, dne))
524             badcp = rval = 1;
525         break;
526     }
527     if (vflag && !badcp)
528         (void)printf("%s -> %s\n", path, to.p_path);
529
530     return 0;
531 }
532
533 static void siginfo(int sig _U_)
534 {
535     sigint = 1;
536 }