blob: a4db99ca88ea66a5e57a7130cca819e4e43d07bd [file] [log] [blame]
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -08001#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
Stephen Smalley8290d102012-01-13 08:53:56 -05008#ifdef HAVE_SELINUX
9#include <selinux/selinux.h>
10#endif
11
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080012#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>
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -070020#include <limits.h>
21
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +010022#include "dynarray.h"
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080023
24// bits for flags argument
Andy McFaddenb33d3412009-04-08 19:25:35 -070025#define LIST_LONG (1 << 0)
26#define LIST_ALL (1 << 1)
27#define LIST_RECURSIVE (1 << 2)
28#define LIST_DIRECTORIES (1 << 3)
Andy McFadden327e6962009-08-18 11:10:03 -070029#define LIST_SIZE (1 << 4)
Brad Fitzpatricke7fe5bf2010-10-25 13:29:53 -070030#define LIST_LONG_NUMERIC (1 << 5)
Kenny Root40dac652011-07-13 09:14:33 -070031#define LIST_CLASSIFY (1 << 6)
Stephen Smalley8290d102012-01-13 08:53:56 -050032#define LIST_MACLABEL (1 << 7)
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080033
34// fwd
35static int listpath(const char *name, int flags);
36
37static 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
51static void mode2str(unsigned mode, char *out)
52{
53 *out++ = mode2kind(mode);
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +010054
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080055 *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
79static 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
89static 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
Andy McFadden9feee022009-09-27 15:13:56 -070099static 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
132static int listfile_size(const char *path, const char *filename, int flags)
Andy McFadden327e6962009-08-18 11:10:03 -0700133{
134 struct stat s;
135
Andy McFadden9feee022009-09-27 15:13:56 -0700136 if (lstat(path, &s) < 0) {
137 fprintf(stderr, "lstat '%s' failed: %s\n", path, strerror(errno));
Andy McFadden327e6962009-08-18 11:10:03 -0700138 return -1;
Andy McFadden9feee022009-09-27 15:13:56 -0700139 }
Andy McFadden327e6962009-08-18 11:10:03 -0700140
141 /* blocks are 512 bytes, we want output to be KB */
Kenny Root40dac652011-07-13 09:14:33 -0700142 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
Andy McFadden327e6962009-08-18 11:10:03 -0700163 return 0;
164}
165
166static int listfile_long(const char *path, int flags)
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800167{
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);
Brad Fitzpatricke7fe5bf2010-10-25 13:29:53 -0700188 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 }
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800195
196 strftime(date, 32, "%Y-%m-%d %H:%M", localtime((const time_t*)&s.st_mtime));
197 date[31] = 0;
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100198
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800199// 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",
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100206 mode, user, group,
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800207 (int) MAJOR(s.st_rdev), (int) MINOR(s.st_rdev),
208 date, name);
209 break;
210 case S_IFREG:
Kenny Rooteb421702010-06-25 09:08:05 -0700211 printf("%s %-8s %-8s %8lld %s %s\n",
212 mode, user, group, s.st_size, date, name);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800213 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;
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100220
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800221 if(len > 255) {
222 linkto[252] = '.';
223 linkto[253] = '.';
224 linkto[254] = '.';
225 linkto[255] = 0;
226 } else {
227 linkto[len] = 0;
228 }
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100229
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800230 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
Stephen Smalley8290d102012-01-13 08:53:56 -0500242static 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
Andy McFadden327e6962009-08-18 11:10:03 -0700308static int listfile(const char *dirname, const char *filename, int flags)
309{
Stephen Smalley8290d102012-01-13 08:53:56 -0500310 if ((flags & LIST_LONG | LIST_SIZE | LIST_CLASSIFY | LIST_MACLABEL) == 0) {
Andy McFadden327e6962009-08-18 11:10:03 -0700311 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
Stephen Smalley8290d102012-01-13 08:53:56 -0500325 if ((flags & LIST_MACLABEL) != 0) {
326 return listfile_maclabel(pathname, flags);
327 } else if ((flags & LIST_LONG) != 0) {
Andy McFadden327e6962009-08-18 11:10:03 -0700328 return listfile_long(pathname, flags);
329 } else /*((flags & LIST_SIZE) != 0)*/ {
Andy McFadden9feee022009-09-27 15:13:56 -0700330 return listfile_size(pathname, filename, flags);
Andy McFadden327e6962009-08-18 11:10:03 -0700331 }
332}
333
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800334static int listdir(const char *name, int flags)
335{
336 char tmp[4096];
337 DIR *d;
338 struct dirent *de;
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700339 strlist_t files = STRLIST_INITIALIZER;
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100340
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800341 d = opendir(name);
342 if(d == 0) {
343 fprintf(stderr, "opendir failed, %s\n", strerror(errno));
344 return -1;
345 }
346
Andy McFadden9feee022009-09-27 15:13:56 -0700347 if ((flags & LIST_SIZE) != 0) {
348 show_total_size(name, d, flags);
349 }
350
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800351 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;
Andy McFadden327e6962009-08-18 11:10:03 -0700354
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700355 strlist_append_dup(&files, de->d_name);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800356 }
357
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700358 strlist_sort(&files);
359 STRLIST_FOREACH(&files, filename, listfile(name, filename, flags));
360 strlist_done(&files);
361
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800362 if (flags & LIST_RECURSIVE) {
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700363 strlist_t subdirs = STRLIST_INITIALIZER;
364
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800365 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
Andy McFadden327e6962009-08-18 11:10:03 -0700376 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);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800380
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)) {
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700397 strlist_append_dup(&subdirs, tmp);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800398 }
399 }
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700400 strlist_sort(&subdirs);
401 STRLIST_FOREACH(&subdirs, path, {
402 printf("\n%s:\n", path);
403 listdir(path, flags);
404 });
405 strlist_done(&subdirs);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800406 }
407
408 closedir(d);
409 return 0;
410}
411
412static 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
Andy McFaddenb33d3412009-04-08 19:25:35 -0700431 if ((flags & LIST_DIRECTORIES) == 0 && S_ISDIR(s.st_mode)) {
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800432 if (flags & LIST_RECURSIVE)
433 printf("\n%s:\n", name);
434 return listdir(name, flags);
435 } else {
Andy McFadden327e6962009-08-18 11:10:03 -0700436 /* yeah this calls stat() again*/
437 return listfile(NULL, name, flags);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800438 }
439}
440
441int ls_main(int argc, char **argv)
442{
443 int flags = 0;
444 int listed = 0;
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100445
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800446 if(argc > 1) {
447 int i;
448 int err = 0;
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700449 strlist_t files = STRLIST_INITIALIZER;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800450
451 for (i = 1; i < argc; i++) {
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700452 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;
Brad Fitzpatricke7fe5bf2010-10-25 13:29:53 -0700458 case 'n': flags |= LIST_LONG | LIST_LONG_NUMERIC; break;
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700459 case 's': flags |= LIST_SIZE; break;
460 case 'R': flags |= LIST_RECURSIVE; break;
461 case 'd': flags |= LIST_DIRECTORIES; break;
Stephen Smalley8290d102012-01-13 08:53:56 -0500462 case 'Z': flags |= LIST_MACLABEL; break;
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700463 case 'a': flags |= LIST_ALL; break;
Kenny Root40dac652011-07-13 09:14:33 -0700464 case 'F': flags |= LIST_CLASSIFY; break;
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700465 default:
466 fprintf(stderr, "%s: Unknown option '-%c'. Aborting.\n", "ls", arg[0]);
467 exit(1);
468 }
469 arg++;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800470 }
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700471 } else {
472 /* not an option ? */
473 strlist_append_dup(&files, argv[i]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800474 }
475 }
476
David 'Digit' Turneraa2106b2010-06-04 10:44:56 -0700477 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 }
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800486 }
David 'Digit' Turnera8d1afb2011-01-06 08:39:44 +0100487
488 // list working directory if no files or directories were specified
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800489 return listpath(".", flags);
490}