1#include <dirent.h> 2#include <errno.h> 3#include <grp.h> 4#include <limits.h> 5#include <pwd.h> 6#include <stddef.h> 7#include <stdio.h> 8#include <stdlib.h> 9#include <string.h> 10#include <sys/stat.h> 11#include <sys/sysmacros.h> 12#include <sys/types.h> 13#include <time.h> 14#include <unistd.h> 15 16#include <selinux/selinux.h> 17 18// simple dynamic array of strings. 19typedef struct { 20 int count; 21 int capacity; 22 void** items; 23} strlist_t; 24 25#define STRLIST_INITIALIZER { 0, 0, NULL } 26 27/* Used to iterate over a strlist_t 28 * _list :: pointer to strlist_t object 29 * _item :: name of local variable name defined within the loop with 30 * type 'char*' 31 * _stmnt :: C statement executed in each iteration 32 * 33 * This macro is only intended for simple uses. Do not add or remove items 34 * to/from the list during iteration. 35 */ 36#define STRLIST_FOREACH(_list,_item,_stmnt) \ 37 do { \ 38 int _nn_##__LINE__ = 0; \ 39 for (;_nn_##__LINE__ < (_list)->count; ++ _nn_##__LINE__) { \ 40 char* _item = (char*)(_list)->items[_nn_##__LINE__]; \ 41 _stmnt; \ 42 } \ 43 } while (0) 44 45static void dynarray_reserve_more( strlist_t *a, int count ) { 46 int old_cap = a->capacity; 47 int new_cap = old_cap; 48 const int max_cap = INT_MAX/sizeof(void*); 49 void** new_items; 50 int new_count = a->count + count; 51 52 if (count <= 0) 53 return; 54 55 if (count > max_cap - a->count) 56 abort(); 57 58 new_count = a->count + count; 59 60 while (new_cap < new_count) { 61 old_cap = new_cap; 62 new_cap += (new_cap >> 2) + 4; 63 if (new_cap < old_cap || new_cap > max_cap) { 64 new_cap = max_cap; 65 } 66 } 67 new_items = realloc(a->items, new_cap*sizeof(void*)); 68 if (new_items == NULL) 69 abort(); 70 71 a->items = new_items; 72 a->capacity = new_cap; 73} 74 75void strlist_init( strlist_t *list ) { 76 list->count = list->capacity = 0; 77 list->items = NULL; 78} 79 80// append a new string made of the first 'slen' characters from 'str' 81// followed by a trailing zero. 82void strlist_append_b( strlist_t *list, const void* str, size_t slen ) { 83 char *copy = malloc(slen+1); 84 memcpy(copy, str, slen); 85 copy[slen] = '\0'; 86 if (list->count >= list->capacity) 87 dynarray_reserve_more(list, 1); 88 list->items[list->count++] = copy; 89} 90 91// append the copy of a given input string to a strlist_t. 92void strlist_append_dup( strlist_t *list, const char *str) { 93 strlist_append_b(list, str, strlen(str)); 94} 95 96// note: strlist_done will free all the strings owned by the list. 97void strlist_done( strlist_t *list ) { 98 STRLIST_FOREACH(list, string, free(string)); 99 free(list->items); 100 list->items = NULL; 101 list->count = list->capacity = 0; 102} 103 104static int strlist_compare_strings(const void* a, const void* b) { 105 const char *sa = *(const char **)a; 106 const char *sb = *(const char **)b; 107 return strcmp(sa, sb); 108} 109 110/* sort the strings in a given list (using strcmp) */ 111void strlist_sort( strlist_t *list ) { 112 if (list->count > 0) { 113 qsort(list->items, (size_t)list->count, sizeof(void*), strlist_compare_strings); 114 } 115} 116 117 118// bits for flags argument 119#define LIST_LONG (1 << 0) 120#define LIST_ALL (1 << 1) 121#define LIST_RECURSIVE (1 << 2) 122#define LIST_DIRECTORIES (1 << 3) 123#define LIST_SIZE (1 << 4) 124#define LIST_LONG_NUMERIC (1 << 5) 125#define LIST_CLASSIFY (1 << 6) 126#define LIST_MACLABEL (1 << 7) 127#define LIST_INODE (1 << 8) 128 129// fwd 130static int listpath(const char *name, int flags); 131 132static char mode2kind(mode_t mode) 133{ 134 switch(mode & S_IFMT){ 135 case S_IFSOCK: return 's'; 136 case S_IFLNK: return 'l'; 137 case S_IFREG: return '-'; 138 case S_IFDIR: return 'd'; 139 case S_IFBLK: return 'b'; 140 case S_IFCHR: return 'c'; 141 case S_IFIFO: return 'p'; 142 default: return '?'; 143 } 144} 145 146void strmode(mode_t mode, char *out) 147{ 148 *out++ = mode2kind(mode); 149 150 *out++ = (mode & 0400) ? 'r' : '-'; 151 *out++ = (mode & 0200) ? 'w' : '-'; 152 if(mode & 04000) { 153 *out++ = (mode & 0100) ? 's' : 'S'; 154 } else { 155 *out++ = (mode & 0100) ? 'x' : '-'; 156 } 157 *out++ = (mode & 040) ? 'r' : '-'; 158 *out++ = (mode & 020) ? 'w' : '-'; 159 if(mode & 02000) { 160 *out++ = (mode & 010) ? 's' : 'S'; 161 } else { 162 *out++ = (mode & 010) ? 'x' : '-'; 163 } 164 *out++ = (mode & 04) ? 'r' : '-'; 165 *out++ = (mode & 02) ? 'w' : '-'; 166 if(mode & 01000) { 167 *out++ = (mode & 01) ? 't' : 'T'; 168 } else { 169 *out++ = (mode & 01) ? 'x' : '-'; 170 } 171 *out = 0; 172} 173 174static void user2str(uid_t uid, char *out, size_t out_size) 175{ 176 struct passwd *pw = getpwuid(uid); 177 if(pw) { 178 strlcpy(out, pw->pw_name, out_size); 179 } else { 180 snprintf(out, out_size, "%d", uid); 181 } 182} 183 184static void group2str(gid_t gid, char *out, size_t out_size) 185{ 186 struct group *gr = getgrgid(gid); 187 if(gr) { 188 strlcpy(out, gr->gr_name, out_size); 189 } else { 190 snprintf(out, out_size, "%d", gid); 191 } 192} 193 194static int show_total_size(const char *dirname, DIR *d, int flags) 195{ 196 struct dirent *de; 197 char tmp[1024]; 198 struct stat s; 199 int sum = 0; 200 201 /* run through the directory and sum up the file block sizes */ 202 while ((de = readdir(d)) != 0) { 203 if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) 204 continue; 205 if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0) 206 continue; 207 208 if (strcmp(dirname, "/") == 0) 209 snprintf(tmp, sizeof(tmp), "/%s", de->d_name); 210 else 211 snprintf(tmp, sizeof(tmp), "%s/%s", dirname, de->d_name); 212 213 if (lstat(tmp, &s) < 0) { 214 fprintf(stderr, "stat failed on %s: %s\n", tmp, strerror(errno)); 215 rewinddir(d); 216 return -1; 217 } 218 219 sum += s.st_blocks / 2; 220 } 221 222 printf("total %d\n", sum); 223 rewinddir(d); 224 return 0; 225} 226 227static int listfile_size(const char *path, const char *filename, struct stat *s, 228 int flags) 229{ 230 if(!s || !path) { 231 return -1; 232 } 233 234 /* blocks are 512 bytes, we want output to be KB */ 235 if ((flags & LIST_SIZE) != 0) { 236 printf("%lld ", (long long)s->st_blocks / 2); 237 } 238 239 if ((flags & LIST_CLASSIFY) != 0) { 240 char filetype = mode2kind(s->st_mode); 241 if (filetype != 'l') { 242 printf("%c ", filetype); 243 } else { 244 struct stat link_dest; 245 if (!stat(path, &link_dest)) { 246 printf("l%c ", mode2kind(link_dest.st_mode)); 247 } else { 248 fprintf(stderr, "stat '%s' failed: %s\n", path, strerror(errno)); 249 printf("l? "); 250 } 251 } 252 } 253 254 printf("%s\n", filename); 255 256 return 0; 257} 258 259static int listfile_long(const char *path, struct stat *s, int flags) 260{ 261 char date[32]; 262 char mode[16]; 263 char user[32]; 264 char group[32]; 265 const char *name; 266 267 if(!s || !path) { 268 return -1; 269 } 270 271 /* name is anything after the final '/', or the whole path if none*/ 272 name = strrchr(path, '/'); 273 if(name == 0) { 274 name = path; 275 } else { 276 name++; 277 } 278 279 strmode(s->st_mode, mode); 280 if (flags & LIST_LONG_NUMERIC) { 281 snprintf(user, sizeof(user), "%u", s->st_uid); 282 snprintf(group, sizeof(group), "%u", s->st_gid); 283 } else { 284 user2str(s->st_uid, user, sizeof(user)); 285 group2str(s->st_gid, group, sizeof(group)); 286 } 287 288 strftime(date, 32, "%Y-%m-%d %H:%M", localtime((const time_t*)&s->st_mtime)); 289 date[31] = 0; 290 291// 12345678901234567890123456789012345678901234567890123456789012345678901234567890 292// MMMMMMMM UUUUUUUU GGGGGGGGG XXXXXXXX YYYY-MM-DD HH:MM NAME (->LINK) 293 294 switch(s->st_mode & S_IFMT) { 295 case S_IFBLK: 296 case S_IFCHR: 297 printf("%s %-8s %-8s %3d, %3d %s %s\n", 298 mode, user, group, 299 major(s->st_rdev), minor(s->st_rdev), 300 date, name); 301 break; 302 case S_IFREG: 303 printf("%s %-8s %-8s %8lld %s %s\n", 304 mode, user, group, (long long)s->st_size, date, name); 305 break; 306 case S_IFLNK: { 307 char linkto[256]; 308 ssize_t len; 309 310 len = readlink(path, linkto, 256); 311 if(len < 0) return -1; 312 313 if(len > 255) { 314 linkto[252] = '.'; 315 linkto[253] = '.'; 316 linkto[254] = '.'; 317 linkto[255] = 0; 318 } else { 319 linkto[len] = 0; 320 } 321 322 printf("%s %-8s %-8s %s %s -> %s\n", 323 mode, user, group, date, name, linkto); 324 break; 325 } 326 default: 327 printf("%s %-8s %-8s %s %s\n", 328 mode, user, group, date, name); 329 330 } 331 return 0; 332} 333 334static int listfile_maclabel(const char *path, struct stat *s) 335{ 336 char mode[16]; 337 char user[32]; 338 char group[32]; 339 char *maclabel = NULL; 340 const char *name; 341 342 if(!s || !path) { 343 return -1; 344 } 345 346 /* name is anything after the final '/', or the whole path if none*/ 347 name = strrchr(path, '/'); 348 if(name == 0) { 349 name = path; 350 } else { 351 name++; 352 } 353 354 lgetfilecon(path, &maclabel); 355 if (!maclabel) { 356 return -1; 357 } 358 359 strmode(s->st_mode, mode); 360 user2str(s->st_uid, user, sizeof(user)); 361 group2str(s->st_gid, group, sizeof(group)); 362 363 switch(s->st_mode & S_IFMT) { 364 case S_IFLNK: { 365 char linkto[256]; 366 ssize_t len; 367 368 len = readlink(path, linkto, sizeof(linkto)); 369 if(len < 0) return -1; 370 371 if((size_t)len > sizeof(linkto)-1) { 372 linkto[sizeof(linkto)-4] = '.'; 373 linkto[sizeof(linkto)-3] = '.'; 374 linkto[sizeof(linkto)-2] = '.'; 375 linkto[sizeof(linkto)-1] = 0; 376 } else { 377 linkto[len] = 0; 378 } 379 380 printf("%s %-8s %-8s %s %s -> %s\n", 381 mode, user, group, maclabel, name, linkto); 382 break; 383 } 384 default: 385 printf("%s %-8s %-8s %s %s\n", 386 mode, user, group, maclabel, name); 387 388 } 389 390 free(maclabel); 391 392 return 0; 393} 394 395static int listfile(const char *dirname, const char *filename, int flags) 396{ 397 struct stat s; 398 399 if ((flags & (LIST_LONG | LIST_SIZE | LIST_CLASSIFY | LIST_MACLABEL | LIST_INODE)) == 0) { 400 printf("%s\n", filename); 401 return 0; 402 } 403 404 char tmp[4096]; 405 const char* pathname = filename; 406 407 if (dirname != NULL) { 408 snprintf(tmp, sizeof(tmp), "%s/%s", dirname, filename); 409 pathname = tmp; 410 } else { 411 pathname = filename; 412 } 413 414 if(lstat(pathname, &s) < 0) { 415 fprintf(stderr, "lstat '%s' failed: %s\n", pathname, strerror(errno)); 416 return -1; 417 } 418 419 if(flags & LIST_INODE) { 420 printf("%8llu ", (unsigned long long)s.st_ino); 421 } 422 423 if ((flags & LIST_MACLABEL) != 0) { 424 return listfile_maclabel(pathname, &s); 425 } else if ((flags & LIST_LONG) != 0) { 426 return listfile_long(pathname, &s, flags); 427 } else /*((flags & LIST_SIZE) != 0)*/ { 428 return listfile_size(pathname, filename, &s, flags); 429 } 430} 431 432static int listdir(const char *name, int flags) 433{ 434 char tmp[4096]; 435 DIR *d; 436 struct dirent *de; 437 strlist_t files = STRLIST_INITIALIZER; 438 439 d = opendir(name); 440 if(d == 0) { 441 fprintf(stderr, "opendir failed, %s\n", strerror(errno)); 442 return -1; 443 } 444 445 if ((flags & LIST_SIZE) != 0) { 446 show_total_size(name, d, flags); 447 } 448 449 while((de = readdir(d)) != 0){ 450 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; 451 if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue; 452 453 strlist_append_dup(&files, de->d_name); 454 } 455 456 strlist_sort(&files); 457 STRLIST_FOREACH(&files, filename, listfile(name, filename, flags)); 458 strlist_done(&files); 459 460 if (flags & LIST_RECURSIVE) { 461 strlist_t subdirs = STRLIST_INITIALIZER; 462 463 rewinddir(d); 464 465 while ((de = readdir(d)) != 0) { 466 struct stat s; 467 int err; 468 469 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) 470 continue; 471 if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0) 472 continue; 473 474 if (!strcmp(name, "/")) 475 snprintf(tmp, sizeof(tmp), "/%s", de->d_name); 476 else 477 snprintf(tmp, sizeof(tmp), "%s/%s", name, de->d_name); 478 479 /* 480 * If the name ends in a '/', use stat() so we treat it like a 481 * directory even if it's a symlink. 482 */ 483 if (tmp[strlen(tmp)-1] == '/') 484 err = stat(tmp, &s); 485 else 486 err = lstat(tmp, &s); 487 488 if (err < 0) { 489 perror(tmp); 490 closedir(d); 491 return -1; 492 } 493 494 if (S_ISDIR(s.st_mode)) { 495 strlist_append_dup(&subdirs, tmp); 496 } 497 } 498 strlist_sort(&subdirs); 499 STRLIST_FOREACH(&subdirs, path, { 500 printf("\n%s:\n", path); 501 listdir(path, flags); 502 }); 503 strlist_done(&subdirs); 504 } 505 506 closedir(d); 507 return 0; 508} 509 510static int listpath(const char *name, int flags) 511{ 512 struct stat s; 513 int err; 514 515 /* 516 * If the name ends in a '/', use stat() so we treat it like a 517 * directory even if it's a symlink. 518 */ 519 if (name[strlen(name)-1] == '/') 520 err = stat(name, &s); 521 else 522 err = lstat(name, &s); 523 524 if (err < 0) { 525 perror(name); 526 return -1; 527 } 528 529 if ((flags & LIST_DIRECTORIES) == 0 && S_ISDIR(s.st_mode)) { 530 if (flags & LIST_RECURSIVE) 531 printf("\n%s:\n", name); 532 return listdir(name, flags); 533 } else { 534 /* yeah this calls stat() again*/ 535 return listfile(NULL, name, flags); 536 } 537} 538 539int ls_main(int argc, char **argv) 540{ 541 int flags = 0; 542 543 if(argc > 1) { 544 int i; 545 int err = 0; 546 strlist_t files = STRLIST_INITIALIZER; 547 548 for (i = 1; i < argc; i++) { 549 if (argv[i][0] == '-') { 550 /* an option ? */ 551 const char *arg = argv[i]+1; 552 while (arg[0]) { 553 switch (arg[0]) { 554 case 'l': flags |= LIST_LONG; break; 555 case 'n': flags |= LIST_LONG | LIST_LONG_NUMERIC; break; 556 case 's': flags |= LIST_SIZE; break; 557 case 'R': flags |= LIST_RECURSIVE; break; 558 case 'd': flags |= LIST_DIRECTORIES; break; 559 case 'Z': flags |= LIST_MACLABEL; break; 560 case 'a': flags |= LIST_ALL; break; 561 case 'F': flags |= LIST_CLASSIFY; break; 562 case 'i': flags |= LIST_INODE; break; 563 default: 564 fprintf(stderr, "%s: Unknown option '-%c'. Aborting.\n", "ls", arg[0]); 565 exit(1); 566 } 567 arg++; 568 } 569 } else { 570 /* not an option ? */ 571 strlist_append_dup(&files, argv[i]); 572 } 573 } 574 575 if (files.count > 0) { 576 STRLIST_FOREACH(&files, path, { 577 if (listpath(path, flags) != 0) { 578 err = EXIT_FAILURE; 579 } 580 }); 581 strlist_done(&files); 582 return err; 583 } 584 } 585 586 // list working directory if no files or directories were specified 587 return listpath(".", flags); 588} 589