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