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