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