1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <sys/types.h>
5#include <dirent.h>
6#include <errno.h>
7
8#include <sys/stat.h>
9#include <unistd.h>
10#include <time.h>
11
12#include <pwd.h>
13#include <grp.h>
14
15#include <linux/kdev_t.h>
16
17// bits for flags argument
18#define LIST_LONG           (1 << 0)
19#define LIST_ALL            (1 << 1)
20#define LIST_RECURSIVE      (1 << 2)
21#define LIST_DIRECTORIES    (1 << 3)
22#define LIST_SIZE           (1 << 4)
23
24// fwd
25static int listpath(const char *name, int flags);
26
27static char mode2kind(unsigned mode)
28{
29    switch(mode & S_IFMT){
30    case S_IFSOCK: return 's';
31    case S_IFLNK: return 'l';
32    case S_IFREG: return '-';
33    case S_IFDIR: return 'd';
34    case S_IFBLK: return 'b';
35    case S_IFCHR: return 'c';
36    case S_IFIFO: return 'p';
37    default: return '?';
38    }
39}
40
41static void mode2str(unsigned mode, char *out)
42{
43    *out++ = mode2kind(mode);
44
45    *out++ = (mode & 0400) ? 'r' : '-';
46    *out++ = (mode & 0200) ? 'w' : '-';
47    if(mode & 04000) {
48        *out++ = (mode & 0100) ? 's' : 'S';
49    } else {
50        *out++ = (mode & 0100) ? 'x' : '-';
51    }
52    *out++ = (mode & 040) ? 'r' : '-';
53    *out++ = (mode & 020) ? 'w' : '-';
54    if(mode & 02000) {
55        *out++ = (mode & 010) ? 's' : 'S';
56    } else {
57        *out++ = (mode & 010) ? 'x' : '-';
58    }
59    *out++ = (mode & 04) ? 'r' : '-';
60    *out++ = (mode & 02) ? 'w' : '-';
61    if(mode & 01000) {
62        *out++ = (mode & 01) ? 't' : 'T';
63    } else {
64        *out++ = (mode & 01) ? 'x' : '-';
65    }
66    *out = 0;
67}
68
69static void user2str(unsigned uid, char *out)
70{
71    struct passwd *pw = getpwuid(uid);
72    if(pw) {
73        strcpy(out, pw->pw_name);
74    } else {
75        sprintf(out, "%d", uid);
76    }
77}
78
79static void group2str(unsigned gid, char *out)
80{
81    struct group *gr = getgrgid(gid);
82    if(gr) {
83        strcpy(out, gr->gr_name);
84    } else {
85        sprintf(out, "%d", gid);
86    }
87}
88
89static int show_total_size(const char *dirname, DIR *d, int flags)
90{
91    struct dirent *de;
92    char tmp[1024];
93    struct stat s;
94    int sum = 0;
95
96    /* run through the directory and sum up the file block sizes */
97    while ((de = readdir(d)) != 0) {
98        if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
99            continue;
100        if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0)
101            continue;
102
103        if (strcmp(dirname, "/") == 0)
104            snprintf(tmp, sizeof(tmp), "/%s", de->d_name);
105        else
106            snprintf(tmp, sizeof(tmp), "%s/%s", dirname, de->d_name);
107
108        if (lstat(tmp, &s) < 0) {
109            fprintf(stderr, "stat failed on %s: %s\n", tmp, strerror(errno));
110            rewinddir(d);
111            return -1;
112        }
113
114        sum += s.st_blocks / 2;
115    }
116
117    printf("total %d\n", sum);
118    rewinddir(d);
119    return 0;
120}
121
122static int listfile_size(const char *path, const char *filename, int flags)
123{
124    struct stat s;
125
126    if (lstat(path, &s) < 0) {
127        fprintf(stderr, "lstat '%s' failed: %s\n", path, strerror(errno));
128        return -1;
129    }
130
131    /* blocks are 512 bytes, we want output to be KB */
132    printf("%lld %s\n", s.st_blocks / 2, filename);
133    return 0;
134}
135
136static int listfile_long(const char *path, int flags)
137{
138    struct stat s;
139    char date[32];
140    char mode[16];
141    char user[16];
142    char group[16];
143    const char *name;
144
145    /* name is anything after the final '/', or the whole path if none*/
146    name = strrchr(path, '/');
147    if(name == 0) {
148        name = path;
149    } else {
150        name++;
151    }
152
153    if(lstat(path, &s) < 0) {
154        return -1;
155    }
156
157    mode2str(s.st_mode, mode);
158    user2str(s.st_uid, user);
159    group2str(s.st_gid, group);
160
161    strftime(date, 32, "%Y-%m-%d %H:%M", localtime((const time_t*)&s.st_mtime));
162    date[31] = 0;
163
164// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
165// MMMMMMMM UUUUUUUU GGGGGGGGG XXXXXXXX YYYY-MM-DD HH:MM NAME (->LINK)
166
167    switch(s.st_mode & S_IFMT) {
168    case S_IFBLK:
169    case S_IFCHR:
170        printf("%s %-8s %-8s %3d, %3d %s %s\n",
171               mode, user, group,
172               (int) MAJOR(s.st_rdev), (int) MINOR(s.st_rdev),
173               date, name);
174        break;
175    case S_IFREG:
176        printf("%s %-8s %-8s %8lld %s %s\n",
177               mode, user, group, s.st_size, date, name);
178        break;
179    case S_IFLNK: {
180        char linkto[256];
181        int len;
182
183        len = readlink(path, linkto, 256);
184        if(len < 0) return -1;
185
186        if(len > 255) {
187            linkto[252] = '.';
188            linkto[253] = '.';
189            linkto[254] = '.';
190            linkto[255] = 0;
191        } else {
192            linkto[len] = 0;
193        }
194
195        printf("%s %-8s %-8s          %s %s -> %s\n",
196               mode, user, group, date, name, linkto);
197        break;
198    }
199    default:
200        printf("%s %-8s %-8s          %s %s\n",
201               mode, user, group, date, name);
202
203    }
204    return 0;
205}
206
207static int listfile(const char *dirname, const char *filename, int flags)
208{
209    if ((flags & (LIST_LONG | LIST_SIZE)) == 0) {
210        printf("%s\n", filename);
211        return 0;
212    }
213
214    char tmp[4096];
215    const char* pathname = filename;
216
217    if (dirname != NULL) {
218        snprintf(tmp, sizeof(tmp), "%s/%s", dirname, filename);
219        pathname = tmp;
220    } else {
221        pathname = filename;
222    }
223
224    if ((flags & LIST_LONG) != 0) {
225        return listfile_long(pathname, flags);
226    } else /*((flags & LIST_SIZE) != 0)*/ {
227        return listfile_size(pathname, filename, flags);
228    }
229}
230
231static int listdir(const char *name, int flags)
232{
233    char tmp[4096];
234    DIR *d;
235    struct dirent *de;
236
237    d = opendir(name);
238    if(d == 0) {
239        fprintf(stderr, "opendir failed, %s\n", strerror(errno));
240        return -1;
241    }
242
243    if ((flags & LIST_SIZE) != 0) {
244        show_total_size(name, d, flags);
245    }
246
247    while((de = readdir(d)) != 0){
248        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
249        if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue;
250
251        listfile(name, de->d_name, flags);
252    }
253
254    if (flags & LIST_RECURSIVE) {
255        rewinddir(d);
256
257        while ((de = readdir(d)) != 0) {
258            struct stat s;
259            int err;
260
261            if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
262                continue;
263            if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0)
264                continue;
265
266            if (!strcmp(name, "/"))
267                snprintf(tmp, sizeof(tmp), "/%s", de->d_name);
268            else
269                snprintf(tmp, sizeof(tmp), "%s/%s", name, de->d_name);
270
271            /*
272             * If the name ends in a '/', use stat() so we treat it like a
273             * directory even if it's a symlink.
274             */
275            if (tmp[strlen(tmp)-1] == '/')
276                err = stat(tmp, &s);
277            else
278                err = lstat(tmp, &s);
279
280            if (err < 0) {
281                perror(tmp);
282                closedir(d);
283                return -1;
284            }
285
286            if (S_ISDIR(s.st_mode)) {
287                printf("\n%s:\n", tmp);
288                listdir(tmp, flags);
289            }
290        }
291    }
292
293    closedir(d);
294    return 0;
295}
296
297static int listpath(const char *name, int flags)
298{
299    struct stat s;
300    int err;
301
302    /*
303     * If the name ends in a '/', use stat() so we treat it like a
304     * directory even if it's a symlink.
305     */
306    if (name[strlen(name)-1] == '/')
307        err = stat(name, &s);
308    else
309        err = lstat(name, &s);
310
311    if (err < 0) {
312        perror(name);
313        return -1;
314    }
315
316    if ((flags & LIST_DIRECTORIES) == 0 && S_ISDIR(s.st_mode)) {
317        if (flags & LIST_RECURSIVE)
318            printf("\n%s:\n", name);
319        return listdir(name, flags);
320    } else {
321        /* yeah this calls stat() again*/
322        return listfile(NULL, name, flags);
323    }
324}
325
326int ls_main(int argc, char **argv)
327{
328    int flags = 0;
329    int listed = 0;
330
331    if(argc > 1) {
332        int i;
333        int err = 0;
334
335        for (i = 1; i < argc; i++) {
336            if (!strcmp(argv[i], "-l")) {
337                flags |= LIST_LONG;
338            } else if (!strcmp(argv[i], "-s")) {
339                flags |= LIST_SIZE;
340            } else if (!strcmp(argv[i], "-a")) {
341                flags |= LIST_ALL;
342            } else if (!strcmp(argv[i], "-R")) {
343                flags |= LIST_RECURSIVE;
344            } else if (!strcmp(argv[i], "-d")) {
345                flags |= LIST_DIRECTORIES;
346            } else {
347                listed++;
348                if(listpath(argv[i], flags) != 0) {
349                    err = EXIT_FAILURE;
350                }
351            }
352        }
353
354        if (listed  > 0) return err;
355    }
356
357    // list working directory if no files or directories were specified
358    return listpath(".", flags);
359}
360