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