1/*
2   Copyright (c) 2009 Frank Lahm <franklahm@gmail.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 <unistd.h>
20#include <sys/types.h>
21#include <stdlib.h>
22#include <stdio.h>
23#include <stdarg.h>
24#include <limits.h>
25#include <string.h>
26#include <errno.h>
27#include <dirent.h>
28#include <fcntl.h>
29#include <ctype.h>
30#include <pwd.h>
31#include <grp.h>
32#include <time.h>
33
34#include <atalk/adouble.h>
35#include <atalk/cnid.h>
36#include "ad.h"
37
38#define ADv2_DIRNAME ".AppleDouble"
39
40#define DIR_DOT_OR_DOTDOT(a) \
41        ((strcmp(a, ".") == 0) || (strcmp(a, "..") == 0))
42
43static volatile sig_atomic_t alarmed;
44
45/* ls options */
46static int ls_a;
47static int ls_l;
48static int ls_R;
49static int ls_d;
50static int ls_u;
51
52/* Used for pretty printing */
53static int first = 1;
54static int recursion;
55
56static char           *netatalk_dirs[] = {
57    ADv2_DIRNAME,
58    ".AppleDB",
59    ".AppleDesktop",
60    NULL
61};
62
63static char *labels[] = {
64    "---",
65    "gry",
66    "gre",
67    "vio",
68    "blu",
69    "yel",
70    "red",
71    "ora"
72};
73
74/*
75  SIGNAL handling:
76  catch SIGINT and SIGTERM which cause clean exit. Ignore anything else.
77*/
78
79static void sig_handler(int signo)
80{
81    alarmed = 1;
82    return;
83}
84
85static void set_signal(void)
86{
87    struct sigaction sv;
88
89    sv.sa_handler = sig_handler;
90    sv.sa_flags = SA_RESTART;
91    sigemptyset(&sv.sa_mask);
92    if (sigaction(SIGTERM, &sv, NULL) < 0)
93        ERROR("error in sigaction(SIGTERM): %s", strerror(errno));
94
95    if (sigaction(SIGINT, &sv, NULL) < 0)
96        ERROR("error in sigaction(SIGINT): %s", strerror(errno));
97
98    memset(&sv, 0, sizeof(struct sigaction));
99    sv.sa_handler = SIG_IGN;
100    sigemptyset(&sv.sa_mask);
101
102    if (sigaction(SIGABRT, &sv, NULL) < 0)
103        ERROR("error in sigaction(SIGABRT): %s", strerror(errno));
104
105    if (sigaction(SIGHUP, &sv, NULL) < 0)
106        ERROR("error in sigaction(SIGHUP): %s", strerror(errno));
107
108    if (sigaction(SIGQUIT, &sv, NULL) < 0)
109        ERROR("error in sigaction(SIGQUIT): %s", strerror(errno));
110}
111
112/*
113  Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
114  Returns pointer to name or NULL.
115*/
116static const char *check_netatalk_dirs(const char *name)
117{
118    int c;
119
120    for (c=0; netatalk_dirs[c]; c++) {
121        if ((strcmp(name, netatalk_dirs[c])) == 0)
122            return netatalk_dirs[c];
123    }
124    return NULL;
125}
126
127
128static void usage_ls(void)
129{
130    printf(
131        "Usage: ad ls [-dRl[u]] [file|dir, ...]\n\n"
132        "  -l Long Output [-u: unix info]:\n"
133        "     <unixinfo ...> <FinderFlags> <AFPAttributes> <Color> <Type> <Creator> <CNID from AppleDouble> <name>\n\n"
134        "     FinderFlags (valid for (f)ile and/or (d)irectory):\n"
135        "       d = On Desktop (f/d)\n"
136        "       e = Hidden extension (f/d)\n"
137        "       m = Shared (can run multiple times) (f)\n"
138        "       n = No INIT resources (f)\n"
139        "       i = Inited (f/d)\n"
140        "       c = Custom icon (f/d)\n"
141        "       t = Stationery (f)\n"
142        "       s = Name locked (f/d)\n"
143        "       b = Bundle (f/d)\n"
144        "       v = Invisible (f/d)\n"
145        "       a = Alias file (f/d)\n\n"
146        "     AFPAttributes:\n"
147        "       y = System (f/d)\n"
148        "       w = No write (f)\n"
149        "       p = Needs backup (f/d)\n"
150        "       r = No rename (f/d)\n"
151        "       l = No delete (f/d)\n"
152        "       o = No copy (f)\n\n"
153        "     Note: any letter appearing in uppercase means the flag is set\n"
154        "           but it's a directory for which the flag is not allowed.\n"
155        );
156}
157
158static void print_numlinks(const struct stat *statp)
159{
160    printf("%5ld", (long)statp->st_nlink);
161}
162
163static void print_owner(const struct stat *statp)
164{
165    struct passwd *pwd = getpwuid(statp->st_uid);
166
167    if (pwd == NULL)
168        printf(" %-8ld", (long)statp->st_uid);
169    else
170        printf(" %-8s", pwd->pw_name);
171}
172
173static void print_group(const struct stat *statp)
174{
175    struct group *grp = getgrgid(statp->st_gid);
176
177    if (grp == NULL)
178        printf(" %-8ld", (long)statp->st_gid);
179    else
180        printf(" %-8s", grp->gr_name);
181}
182
183static void print_size(const struct stat *statp)
184{
185    switch (statp->st_mode & S_IFMT) {
186    case S_IFCHR:
187    case S_IFBLK:
188        printf("%4u,%4u", (unsigned)(statp->st_rdev >> 8),
189               (unsigned)(statp->st_rdev & 0xFF));
190        break;
191    default:
192        printf("%9lu", (unsigned long)statp->st_size);
193    }
194}
195
196static void print_date(const struct stat *statp)
197{
198    time_t now;
199    double diff;
200    char buf[100], *fmt;
201
202    if (time(&now) == -1) {
203        printf(" ????????????");
204        return;
205    }
206    diff = difftime(now, statp->st_mtime);
207    if (diff < 0 || diff > 60 * 60 * 24 * 182.5)
208        fmt = "%b %e  %Y";
209    else
210        fmt = "%b %e %H:%M";
211    strftime(buf, sizeof(buf), fmt, localtime(&statp->st_mtime));
212    printf(" %s", buf);
213}
214
215static void print_flags(char *path, afpvol_t *vol, const struct stat *st)
216{
217    int adflags = 0;
218    struct adouble ad;
219    char *FinderInfo;
220    uint16_t FinderFlags;
221    uint16_t AFPattributes;
222    char type[5] = "----";
223    char creator[5] = "----";
224    int i;
225    uint32_t cnid;
226
227    if (S_ISDIR(st->st_mode))
228        adflags = ADFLAGS_DIR;
229
230    if (vol->vol == NULL || vol->vol->v_path == NULL)
231        return;
232
233    ad_init(&ad, vol->vol);
234
235    if ( ad_metadata(path, adflags, &ad) < 0 )
236        return;
237
238    FinderInfo = ad_entry(&ad, ADEID_FINDERI);
239
240    memcpy(&FinderFlags, FinderInfo + 8, 2);
241    FinderFlags = ntohs(FinderFlags);
242
243    memcpy(type, FinderInfo, 4);
244    memcpy(creator, FinderInfo + 4, 4);
245
246    ad_getattr(&ad, &AFPattributes);
247    AFPattributes = ntohs(AFPattributes);
248
249    /*
250      Finder flags. Lowercase means valid, uppercase means invalid because
251      object is a dir and flag is only valid for files.
252    */
253    putchar(' ');
254    if (FinderFlags & FINDERINFO_ISONDESK)
255        putchar('d');
256    else
257        putchar('-');
258
259    if (FinderFlags & FINDERINFO_HIDEEXT)
260        putchar('e');
261    else
262        putchar('-');
263
264    if (FinderFlags & FINDERINFO_ISHARED) {
265        if (adflags & ADFLAGS_DIR)
266            putchar('M');
267        else
268            putchar('m');
269    } else
270        putchar('-');
271
272    if (FinderFlags & FINDERINFO_HASNOINITS) {
273        if (adflags & ADFLAGS_DIR)
274            putchar('N');
275        else
276            putchar('n');
277    } else
278        putchar('-');
279
280    if (FinderFlags & FINDERINFO_HASBEENINITED)
281        putchar('i');
282    else
283        putchar('-');
284
285    if (FinderFlags & FINDERINFO_HASCUSTOMICON)
286        putchar('c');
287    else
288        putchar('-');
289
290    if (FinderFlags & FINDERINFO_ISSTATIONNERY) {
291        if (adflags & ADFLAGS_DIR)
292            putchar('T');
293        else
294            putchar('t');
295    } else
296        putchar('-');
297
298    if (FinderFlags & FINDERINFO_NAMELOCKED)
299        putchar('s');
300    else
301        putchar('-');
302
303    if (FinderFlags & FINDERINFO_HASBUNDLE)
304        putchar('b');
305    else
306        putchar('-');
307
308    if (FinderFlags & FINDERINFO_INVISIBLE)
309        putchar('v');
310    else
311        putchar('-');
312
313    if (FinderFlags & FINDERINFO_ISALIAS)
314        putchar('a');
315    else
316        putchar('-');
317
318    putchar(' ');
319
320    /* AFP attributes */
321    if (AFPattributes & ATTRBIT_SYSTEM)
322        putchar('y');
323    else
324        putchar('-');
325
326    if (AFPattributes & ATTRBIT_NOWRITE) {
327        if (adflags & ADFLAGS_DIR)
328            putchar('W');
329        else
330            putchar('w');
331    } else
332        putchar('-');
333
334    if (AFPattributes & ATTRBIT_BACKUP)
335        putchar('p');
336    else
337        putchar('-');
338
339    if (AFPattributes & ATTRBIT_NORENAME)
340        putchar('r');
341    else
342        putchar('-');
343
344    if (AFPattributes & ATTRBIT_NODELETE)
345        putchar('l');
346    else
347        putchar('-');
348
349    if (AFPattributes & ATTRBIT_NOCOPY) {
350        if (adflags & ADFLAGS_DIR)
351            putchar('O');
352        else
353            putchar('o');
354    } else
355        putchar('-');
356
357    /* Color */
358    printf(" %s ", labels[(FinderFlags & FINDERINFO_COLOR) >> 1]);
359
360    /* Type & Creator */
361    for(i=0; i<4; i++) {
362        if (isalnum(type[i]))
363            putchar(type[i]);
364        else
365            putchar('-');
366    }
367    putchar(' ');
368    for(i=0; i<4; i++) {
369        if (isalnum(creator[i]))
370            putchar(creator[i]);
371        else
372            putchar('-');
373    }
374    putchar(' ');
375
376    /* CNID */
377    cnid = ad_forcegetid(&ad);
378    if (cnid)
379        printf(" %10u ", ntohl(cnid));
380    else
381        printf(" !ADVOL_CACHE ");
382
383    ad_close(&ad, ADFLAGS_HF);
384}
385
386#define TYPE(b) ((st->st_mode & (S_IFMT)) == (b))
387#define MODE(b) ((st->st_mode & (b)) == (b))
388
389static void print_mode(const struct stat *st)
390{
391    if (TYPE(S_IFBLK))
392        putchar('b');
393    else if (TYPE(S_IFCHR))
394        putchar('c');
395    else if (TYPE(S_IFDIR))
396        putchar('d');
397    else if (TYPE(S_IFIFO))
398        putchar('p');
399    else if (TYPE(S_IFREG))
400        putchar('-');
401    else if (TYPE(S_IFLNK))
402        putchar('l');
403    else if (TYPE(S_IFSOCK))
404        putchar('s');
405    else
406        putchar('?');
407    putchar(MODE(S_IRUSR) ? 'r' : '-');
408    putchar(MODE(S_IWUSR) ? 'w' : '-');
409    if (MODE(S_ISUID)) {
410        if (MODE(S_IXUSR))
411            putchar('s');
412        else
413            putchar('S');
414    }
415    else if (MODE(S_IXUSR))
416        putchar('x');
417    else
418        putchar('-');
419    putchar(MODE(S_IRGRP) ? 'r' : '-');
420    putchar(MODE(S_IWGRP) ? 'w' : '-');
421    if (MODE(S_ISGID)) {
422        if (MODE(S_IXGRP))
423            putchar('s');
424        else
425            putchar('S');
426    }
427    else if (MODE(S_IXGRP))
428        putchar('x');
429    else
430        putchar('-');
431    putchar(MODE(S_IROTH) ? 'r' : '-');
432    putchar(MODE(S_IWOTH) ? 'w' : '-');
433    if (MODE(S_IFDIR) && MODE(S_ISVTX)) {
434        if (MODE(S_IXOTH))
435            putchar('t');
436        else
437            putchar('T');
438    }
439    else if (MODE(S_IXOTH))
440        putchar('x');
441    else
442        putchar('-');
443}
444#undef TYPE
445#undef MODE
446
447static int ad_print(char *path, const struct stat *st, afpvol_t *vol)
448{
449    if ( ! ls_l) {
450        printf("%s  ", path);
451        if (ls_d)
452            printf("\n");
453        return 0;
454    }
455
456    /* Long output */
457    if (ls_u) {
458        print_mode(st);
459        print_numlinks(st);
460        print_owner(st);
461        print_group(st);
462        print_size(st);
463        print_date(st);
464    }
465    print_flags(path, vol, st);
466    printf("  %s\n", path);
467
468
469    return 0;
470}
471
472static int ad_ls_r(char *path, afpvol_t *vol)
473{
474    int ret = 0, cwd, dirprinted = 0, dirempty;
475    const char *name;
476    char *tmp;
477    static char cwdpath[MAXPATHLEN+1];
478    DIR *dp;
479    struct dirent *ep;
480    static struct stat st;      /* Save some stack space */
481
482    if ( first)
483        cwdpath[0] = 0;
484    else
485        strcat(cwdpath, "/");
486
487    strcat(cwdpath, path);
488    first = 0;
489
490    if (lstat(path, &st) < 0) {
491        perror("Can't stat");
492        return -1;
493    }
494    /* If its a file or a dir with -d option call ad_print and return */
495    if (S_ISREG(st.st_mode) || ls_d)
496        return ad_print(path, &st, vol);
497
498    /* Its a dir: chdir to it remembering where we started */
499    if ((cwd = open(".", O_RDONLY)) == -1) {
500        perror("Cant open .");
501        return -1;
502    }
503    if (chdir(path) != 0) {
504        perror("Cant chdir");
505        close(cwd);
506        return -1;
507    }
508
509    if ((dp = opendir (".")) == NULL) {
510        perror("Couldn't opendir .");
511        return -1;
512    }
513
514    /* First run: print everything */
515    dirempty = 1;
516    while ((ep = readdir (dp))) {
517        if (alarmed) {
518            ret = -1;
519            goto exit;
520        }
521
522        /* Check if its "." or ".." */
523        if (DIR_DOT_OR_DOTDOT(ep->d_name))
524            continue;
525
526        /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
527        if ((name = check_netatalk_dirs(ep->d_name)) != NULL)
528            continue;
529
530        if ((ep->d_name[0] == '.') && ! ls_a)
531            continue;
532
533        dirempty = 0;
534
535        if (recursion && ! dirprinted) {
536            printf("\n%s:\n", cwdpath);
537            dirprinted = 1;
538        }
539
540        if (lstat(ep->d_name, &st) < 0) {
541            perror("Can't stat");
542            return -1;
543        }
544
545        ret = ad_print(ep->d_name, &st, vol);
546        if (ret != 0)
547            goto exit;
548    }
549
550    if (! ls_l && ! dirempty)
551        printf("\n");
552
553    /* Second run: recurse to dirs */
554    if (ls_R) {
555        rewinddir(dp);
556        while ((ep = readdir (dp))) {
557            if (alarmed) {
558                ret = -1;
559                goto exit;
560            }
561
562            /* Check if its "." or ".." */
563            if (DIR_DOT_OR_DOTDOT(ep->d_name))
564                continue;
565
566            /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
567            if ((name = check_netatalk_dirs(ep->d_name)) != NULL)
568                continue;
569
570            if ((ret = lstat(ep->d_name, &st)) < 0) {
571                perror("Can't stat");
572                goto exit;
573            }
574
575            /* Recursion */
576            if (S_ISDIR(st.st_mode)) {
577                recursion = 1;
578                ret = ad_ls_r(ep->d_name, vol);
579            }
580            if (ret != 0)
581                goto exit;
582        }
583    }
584
585exit:
586    closedir(dp);
587    fchdir(cwd);
588    close(cwd);
589
590    tmp = strrchr(cwdpath, '/');
591    if (tmp)
592        *tmp = 0;
593
594    return ret;
595}
596
597int ad_ls(int argc, char **argv, AFPObj *obj)
598{
599    int c, firstarg;
600    afpvol_t vol;
601    struct stat st;
602
603    while ((c = getopt(argc, argv, ":adlRu")) != -1) {
604        switch(c) {
605        case 'a':
606            ls_a = 1;
607            break;
608        case 'd':
609            ls_d = 1;
610            break;
611        case 'l':
612            ls_l = 1;
613            break;
614        case 'R':
615            ls_R = 1;
616            break;
617        case 'u':
618            ls_u = 1;
619            break;
620        case ':':
621        case '?':
622            usage_ls();
623            return -1;
624            break;
625        }
626
627    }
628
629    set_signal();
630    cnid_init();
631
632    if ((argc - optind) == 0) {
633        openvol(obj, ".", &vol);
634        ad_ls_r(".", &vol);
635        closevol(&vol);
636    }
637    else {
638        int havefile = 0;
639
640        firstarg = optind;
641
642        /* First run: only print files from argv paths */
643        while(optind < argc) {
644            if (stat(argv[optind], &st) != 0)
645                goto next;
646            if (S_ISDIR(st.st_mode))
647                goto next;
648
649            havefile = 1;
650            first = 1;
651            recursion = 0;
652
653            openvol(obj, argv[optind], &vol);
654            ad_ls_r(argv[optind], &vol);
655            closevol(&vol);
656        next:
657            optind++;
658        }
659        if (havefile && (! ls_l))
660            printf("\n");
661
662        /* Second run: print dirs */
663        optind = firstarg;
664        while(optind < argc) {
665            if (stat(argv[optind], &st) != 0)
666                goto next2;
667            if ( ! S_ISDIR(st.st_mode))
668                goto next2;
669            if ((optind > firstarg) || havefile)
670                printf("\n%s:\n", argv[optind]);
671
672            first = 1;
673            recursion = 0;
674
675            openvol(obj, argv[optind], &vol);
676            ad_ls_r(argv[optind], &vol);
677            closevol(&vol);
678
679        next2:
680            optind++;
681        }
682
683
684    }
685
686    return 0;
687}
688