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