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/bstrlib.h>
36 #include <atalk/bstradd.h>
37 #include <atalk/queue.h>
38 
39 #include "ad.h"
40 
41 #define STRIP_TRAILING_SLASH(p) {                                   \
42         while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/')  \
43             *--(p).p_end = 0;                                       \
44     }
45 
46 static afpvol_t volume;
47 
48 static cnid_t did, pdid;
49 static int Rflag;
50 static volatile sig_atomic_t alarmed;
51 static int badrm, rval;
52 
53 static char           *netatalk_dirs[] = {
54     ".AppleDB",
55     ".AppleDesktop",
56     NULL
57 };
58 
59 /* Forward declarations */
60 static int rm(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf);
61 
62 /*
63   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
64   Returns pointer to name or NULL.
65 */
check_netatalk_dirs(const char * name)66 static const char *check_netatalk_dirs(const char *name)
67 {
68     int c;
69 
70     for (c=0; netatalk_dirs[c]; c++) {
71         if ((strcmp(name, netatalk_dirs[c])) == 0)
72             return netatalk_dirs[c];
73     }
74     return NULL;
75 }
76 
upfunc(void)77 static void upfunc(void)
78 {
79     did = pdid;
80 }
81 
82 /*
83   SIGNAL handling:
84   catch SIGINT and SIGTERM which cause clean exit. Ignore anything else.
85 */
86 
sig_handler(int signo)87 static void sig_handler(int signo)
88 {
89     alarmed = 1;
90     return;
91 }
92 
set_signal(void)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 
usage_rm(void)120 static void usage_rm(void)
121 {
122     printf(
123         "Usage: ad rm [-vR] <file|dir> [<file|dir> ...]\n\n"
124         "The rm utility attempts to remove the non-directory type files specified\n"
125         "on the command line.\n"
126         "If the files and directories reside on an AFP volume, the corresponding\n"
127         "CNIDs are deleted from the volumes database.\n\n"
128         "The options are as follows:\n\n"
129         "   -R   Attempt to remove the file hierarchy rooted in each file argument.\n"
130         "   -v   Be verbose when deleting files, showing them as they are removed.\n"
131         );
132     exit(EXIT_FAILURE);
133 }
134 
ad_rm(int argc,char * argv[],AFPObj * obj)135 int ad_rm(int argc, char *argv[], AFPObj *obj)
136 {
137     int ch;
138 
139     pdid = htonl(1);
140     did = htonl(2);
141 
142     while ((ch = getopt(argc, argv, "vR")) != -1)
143         switch (ch) {
144         case 'R':
145             Rflag = 1;
146             break;
147         case 'v':
148             vflag = 1;
149             break;
150         default:
151             usage_rm();
152             break;
153         }
154     argc -= optind;
155     argv += optind;
156 
157     if (argc < 1)
158         usage_rm();
159 
160     set_signal();
161     cnid_init();
162 
163     /* Set end of argument list */
164     argv[argc] = NULL;
165 
166     for (int i = 0; argv[i] != NULL; i++) {
167         /* Load .volinfo file for source */
168         openvol(obj, argv[i], &volume);
169 
170         if (nftw(argv[i], rm, upfunc, 20, FTW_DEPTH | FTW_PHYS) == -1) {
171             if (alarmed) {
172                 SLOG("...break");
173             } else {
174                 SLOG("Error: %s", argv[i]);
175             }
176             closevol(&volume);
177         }
178     }
179     return rval;
180 }
181 
rm(const char * path,const struct stat * statp,int tflag,struct FTW * ftw)182 static int rm(const char *path,
183               const struct stat *statp,
184               int tflag,
185               struct FTW *ftw)
186 {
187     cnid_t cnid;
188 
189     if (alarmed)
190         return -1;
191 
192     const char *dir = strrchr(path, '/');
193     if (dir == NULL)
194         dir = path;
195     else
196         dir++;
197     if (check_netatalk_dirs(dir) != NULL)
198         return FTW_SKIP_SUBTREE;
199 
200     switch (statp->st_mode & S_IFMT) {
201 
202     case S_IFLNK:
203         if (volume.vol->v_path) {
204             if ((volume.vol->v_adouble == AD_VERSION2)
205                 && (strstr(path, ".AppleDouble") != NULL)) {
206                 /* symlink inside adouble dir */
207                 if (unlink(path) != 0)
208                     badrm = rval = 1;
209                 break;
210             }
211 
212             /* Get CNID of Parent and add new childir to CNID database */
213             pdid = did;
214             if ((cnid = cnid_for_path(volume.vol->v_cdb, volume.vol->v_path, path, &did)) == CNID_INVALID) {
215                 SLOG("Error resolving CNID for %s", path);
216                 return -1;
217             }
218             if (cnid_delete(volume.vol->v_cdb, cnid) != 0) {
219                 SLOG("Error removing CNID %u for %s", ntohl(cnid), path);
220                 return -1;
221             }
222         }
223 
224         if (unlink(path) != 0) {
225             badrm = rval = 1;
226             break;
227         }
228 
229         break;
230 
231     case S_IFDIR:
232         if (!Rflag) {
233             SLOG("%s is a directory", path);
234             return FTW_SKIP_SUBTREE;
235         }
236 
237         if (volume.vol->v_path) {
238             if ((volume.vol->v_adouble == AD_VERSION2)
239                 && (strstr(path, ".AppleDouble") != NULL)) {
240                 /* should be adouble dir itself */
241                 if (rmdir(path) != 0) {
242                     SLOG("Error removing dir \"%s\": %s", path, strerror(errno));
243                     badrm = rval = 1;
244                     return -1;
245                 }
246                 break;
247             }
248 
249             /* Get CNID of Parent and add new childir to CNID database */
250             if ((did = cnid_for_path(volume.vol->v_cdb, volume.vol->v_path, path, &pdid)) == CNID_INVALID) {
251                 SLOG("Error resolving CNID for %s", path);
252                 return -1;
253             }
254             if (cnid_delete(volume.vol->v_cdb, did) != 0) {
255                 SLOG("Error removing CNID %u for %s", ntohl(did), path);
256                 return -1;
257             }
258         }
259 
260         if (rmdir(path) != 0) {
261             SLOG("Error removing dir \"%s\": %s", path, strerror(errno));
262             badrm = rval = 1;
263             return -1;
264         }
265 
266         break;
267 
268     case S_IFBLK:
269     case S_IFCHR:
270         SLOG("%s is a device file.", path);
271         badrm = rval = 1;
272         break;
273 
274     case S_IFSOCK:
275         SLOG("%s is a socket.", path);
276         badrm = rval = 1;
277         break;
278 
279     case S_IFIFO:
280         SLOG("%s is a FIFO.", path);
281         badrm = rval = 1;
282         break;
283 
284     default:
285         if (volume.vol->v_path) {
286             if ((volume.vol->v_adouble == AD_VERSION2)
287                 && (strstr(path, ".AppleDouble") != NULL)) {
288                 /* file in adouble dir */
289                 if (unlink(path) != 0)
290                     badrm = rval = 1;
291                 break;
292             }
293 
294             /* Get CNID of Parent and add new childir to CNID database */
295             pdid = did;
296             if ((cnid = cnid_for_path(volume.vol->v_cdb, volume.vol->v_path, path, &did)) == CNID_INVALID) {
297                 SLOG("Error resolving CNID for %s", path);
298                 return -1;
299             }
300             if (cnid_delete(volume.vol->v_cdb, cnid) != 0) {
301                 SLOG("Error removing CNID %u for %s", ntohl(cnid), path);
302                 return -1;
303             }
304 
305             /* Ignore errors, because with -R adouble stuff is always alread gone */
306             volume.vol->vfs->vfs_deletefile(volume.vol, -1, path);
307         }
308 
309         if (unlink(path) != 0) {
310             badrm = rval = 1;
311             break;
312         }
313 
314         break;
315     }
316 
317     if (vflag && !badrm)
318         (void)printf("%s\n", path);
319 
320     return 0;
321 }
322