source: mainline/uspace/app/bdsh/cmds/modules/ls/ls.c@ de9e28e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since de9e28e was de9e28e, checked in by GitHub <noreply@…>, 6 years ago

Merge pull request #136 from matthieuriolo/ls_human_readable

Implements option -1 for single column output.
Changes printing of sizes to use human-readable format.
Add option -e "exact size" to print number of bytes instead.

  • Property mode set to 100644
File size: 12.1 KB
Line 
1/*
2 * Copyright (c) 2008 Tim Post
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * NOTE:
31 * This is a bit of an ugly hack, working around the absence of fstat / etc.
32 * As more stuff is completed and exposed in libc, this will improve
33 */
34
35#include <errno.h>
36#include <str_error.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <stdint.h>
40#include <dirent.h>
41#include <getopt.h>
42#include <vfs/vfs.h>
43#include <str.h>
44#include <cap.h>
45
46#include "ls.h"
47#include "errors.h"
48#include "config.h"
49#include "util.h"
50#include "entry.h"
51#include "cmds.h"
52
53static const char *cmdname = "ls";
54
55static ls_job_t ls;
56
57static struct option const long_options[] = {
58 { "help", no_argument, 0, 'h' },
59 { "unsort", no_argument, 0, 'u' },
60 { "recursive", no_argument, 0, 'r' },
61 { "exact-size", no_argument, 0, 'e' },
62 { "single-column", no_argument, 0, '1' },
63 { 0, 0, 0, 0 }
64};
65
66/* Prototypes for the ls command, excluding entry points. */
67static unsigned int ls_start(ls_job_t *);
68static errno_t ls_print(struct dir_elem_t *);
69static errno_t ls_print_single_column(struct dir_elem_t *);
70static int ls_cmp_type_name(const void *, const void *);
71static int ls_cmp_name(const void *, const void *);
72static signed int ls_scan_dir(const char *, DIR *, struct dir_elem_t **);
73static unsigned int ls_recursive(const char *, DIR *);
74static unsigned int ls_scope(const char *, struct dir_elem_t *);
75
76static unsigned int ls_start(ls_job_t *ls)
77{
78 ls->recursive = 0;
79 ls->sort = 1;
80
81 ls->exact_size = false;
82 ls->single_column = false;
83 ls->printer = ls_print;
84 return 1;
85}
86
87/** Print an entry.
88 *
89 * ls_print currently does nothing more than print the entry.
90 * In the future, we will likely pass the absolute path, and
91 * some sort of ls_options structure that controls how each
92 * entry is printed and what is printed about it.
93 *
94 * Now we just print basic DOS style lists.
95 *
96 * @param de Directory element.
97 */
98static errno_t ls_print(struct dir_elem_t *de)
99{
100 int width = 13;
101
102 if (de->s.is_file) {
103 if (ls.exact_size) {
104 printf("%-40s\t%*llu\n", de->name, width, (long long) de->s.size);
105 return EOK;
106 }
107
108 cap_spec_t cap;
109 cap_from_blocks(de->s.size, 1, &cap);
110 cap_simplify(&cap);
111
112 char *rptr;
113 errno_t rc = cap_format(&cap, &rptr);
114 if (rc != EOK) {
115 return rc;
116 }
117
118 char *sep = str_rchr(rptr, ' ');
119 if (sep == NULL) {
120 free(rptr);
121 return ENOENT;
122 }
123
124 *sep = '\0';
125
126 printf("%-40s\t%*s %2s\n", de->name, width - 3, rptr, sep + 1);
127 free(rptr);
128 } else if (de->s.is_directory)
129 printf("%-40s\t%*s\n", de->name, width, "<dir>");
130 else
131 printf("%-40s\n", de->name);
132
133 return EOK;
134}
135
136static errno_t ls_print_single_column(struct dir_elem_t *de)
137{
138 if (de->s.is_file) {
139 printf("%s\n", de->name);
140 } else {
141 printf("%s/\n", de->name);
142 }
143
144 return EOK;
145}
146
147/** Compare 2 directory elements.
148 *
149 * It compares 2 elements of a directory : a file is considered
150 * as bigger than a directory, and if they have the same type,
151 * they are compared alphabetically.
152 *
153 * @param a Pointer to the structure of the first element.
154 * @param b Pointer to the structure of the second element.
155 *
156 * @return -1 if a < b, 1 otherwise.
157 */
158static int ls_cmp_type_name(const void *a, const void *b)
159{
160 struct dir_elem_t const *da = a;
161 struct dir_elem_t const *db = b;
162
163 if ((da->s.is_directory && db->s.is_file) ||
164 ((da->s.is_directory == db->s.is_directory) &&
165 str_cmp(da->name, db->name) < 0))
166 return -1;
167 else
168 return 1;
169}
170
171/** Compare directories/files per name
172 *
173 * This comparision ignores the type of
174 * the node. Sorted will strictly by name.
175 *
176 */
177static int ls_cmp_name(const void *a, const void *b)
178{
179 struct dir_elem_t const *da = a;
180 struct dir_elem_t const *db = b;
181
182 return str_cmp(da->name, db->name);
183}
184
185/** Scan a directory.
186 *
187 * Scan the content of a directory and print it.
188 *
189 * @param d Name of the directory.
190 * @param dirp Directory stream.
191 * @param sort 1 if the output must be sorted,
192 * 0 otherwise.
193 */
194static signed int ls_scan_dir(const char *d, DIR *dirp,
195 struct dir_elem_t **dir_list_ptr)
196{
197 int alloc_blocks = 20;
198 int i;
199 int nbdirs = 0;
200 errno_t rc;
201 int len;
202 char *buff;
203 struct dir_elem_t *tmp;
204 struct dir_elem_t *tosort;
205 struct dirent *dp;
206
207 if (!dirp)
208 return -1;
209
210 buff = (char *) malloc(PATH_MAX);
211 if (!buff) {
212 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
213 return -1;
214 }
215
216 tosort = (struct dir_elem_t *) malloc(alloc_blocks * sizeof(*tosort));
217 if (!tosort) {
218 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
219 free(buff);
220 return -1;
221 }
222
223 while ((dp = readdir(dirp))) {
224 if (nbdirs + 1 > alloc_blocks) {
225 alloc_blocks += alloc_blocks;
226
227 tmp = (struct dir_elem_t *) realloc(tosort,
228 alloc_blocks * sizeof(struct dir_elem_t));
229 if (!tmp) {
230 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
231 goto out;
232 }
233 tosort = tmp;
234 }
235
236 /* fill the name field */
237 tosort[nbdirs].name = (char *) malloc(str_size(dp->d_name) + 1);
238 if (!tosort[nbdirs].name) {
239 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
240 goto out;
241 }
242
243 str_cpy(tosort[nbdirs].name, str_size(dp->d_name) + 1, dp->d_name);
244 len = snprintf(buff, PATH_MAX - 1, "%s/%s", d, tosort[nbdirs].name);
245 buff[len] = '\0';
246
247 rc = vfs_stat_path(buff, &tosort[nbdirs++].s);
248 if (rc != EOK) {
249 printf("ls: skipping bogus node %s\n", buff);
250 printf("error=%s\n", str_error_name(rc));
251 goto out;
252 }
253 }
254
255 if (ls.sort) {
256 int (*compar)(const void *, const void *);
257 compar = ls.single_column ? ls_cmp_name : ls_cmp_type_name;
258 qsort(&tosort[0], nbdirs, sizeof(struct dir_elem_t), compar);
259 }
260
261 for (i = 0; i < nbdirs; i++) {
262 if (ls.printer(&tosort[i]) != EOK) {
263 cli_error(CL_ENOMEM, "%s: Out of memory", cmdname);
264 goto out;
265 }
266 }
267
268 /* Populate the directory list. */
269 if (ls.recursive && nbdirs > 0) {
270 tmp = (struct dir_elem_t *) realloc(*dir_list_ptr,
271 nbdirs * sizeof(struct dir_elem_t));
272 if (!tmp) {
273 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
274 goto out;
275 }
276 *dir_list_ptr = tmp;
277
278 for (i = 0; i < nbdirs; i++) {
279 (*dir_list_ptr)[i].name = str_dup(tosort[i].name);
280 if (!(*dir_list_ptr)[i].name) {
281 cli_error(CL_ENOMEM, "ls: failed to scan %s", d);
282 goto out;
283 }
284 }
285 }
286
287out:
288 for (i = 0; i < nbdirs; i++)
289 free(tosort[i].name);
290 free(tosort);
291 free(buff);
292
293 return nbdirs;
294}
295
296/** Visit a directory recursively.
297 *
298 * ls_recursive visits all the subdirectories recursively and
299 * prints the files and directories in them.
300 *
301 * @param path Path the current directory being visited.
302 * @param dirp Directory stream.
303 */
304static unsigned int ls_recursive(const char *path, DIR *dirp)
305{
306 int i, nbdirs, ret;
307 unsigned int scope;
308 char *subdir_path;
309 DIR *subdirp;
310 struct dir_elem_t *dir_list;
311
312 const char *const trailing_slash = "/";
313
314 nbdirs = 0;
315 dir_list = (struct dir_elem_t *) malloc(sizeof(struct dir_elem_t));
316
317 printf("\n%s:\n", path);
318
319 subdir_path = (char *) malloc(PATH_MAX);
320 if (!subdir_path) {
321 ret = CMD_FAILURE;
322 goto out;
323 }
324
325 nbdirs = ls_scan_dir(path, dirp, &dir_list);
326 if (nbdirs == -1) {
327 ret = CMD_FAILURE;
328 goto out;
329 }
330
331 for (i = 0; i < nbdirs; ++i) {
332 memset(subdir_path, 0, PATH_MAX);
333
334 if (str_size(subdir_path) + str_size(path) + 1 <= PATH_MAX)
335 str_append(subdir_path, PATH_MAX, path);
336 if (path[str_size(path) - 1] != '/' &&
337 str_size(subdir_path) + str_size(trailing_slash) + 1 <= PATH_MAX)
338 str_append(subdir_path, PATH_MAX, trailing_slash);
339 if (str_size(subdir_path) +
340 str_size(dir_list[i].name) + 1 <= PATH_MAX)
341 str_append(subdir_path, PATH_MAX, dir_list[i].name);
342
343 scope = ls_scope(subdir_path, &dir_list[i]);
344 switch (scope) {
345 case LS_FILE:
346 break;
347 case LS_DIR:
348 subdirp = opendir(subdir_path);
349 if (!subdirp) {
350 /* May have been deleted between scoping it and opening it */
351 cli_error(CL_EFAIL, "Could not stat %s", dir_list[i].name);
352 ret = CMD_FAILURE;
353 goto out;
354 }
355
356 ret = ls_recursive(subdir_path, subdirp);
357 closedir(subdirp);
358 if (ret == CMD_FAILURE)
359 goto out;
360 break;
361 case LS_BOGUS:
362 ret = CMD_FAILURE;
363 goto out;
364 }
365 }
366
367 ret = CMD_SUCCESS;
368
369out:
370 for (i = 0; i < nbdirs; i++)
371 free(dir_list[i].name);
372 free(dir_list);
373 free(subdir_path);
374
375 return ret;
376}
377
378static unsigned int ls_scope(const char *path, struct dir_elem_t *de)
379{
380 if (vfs_stat_path(path, &de->s) != EOK) {
381 cli_error(CL_ENOENT, "%s", path);
382 return LS_BOGUS;
383 }
384
385 if (de->s.is_file)
386 return LS_FILE;
387 else if (de->s.is_directory)
388 return LS_DIR;
389
390 return LS_BOGUS;
391}
392
393void help_cmd_ls(unsigned int level)
394{
395 if (level == HELP_SHORT) {
396 printf("`%s' lists files and directories.\n", cmdname);
397 } else {
398 help_cmd_ls(HELP_SHORT);
399 printf(
400 "Usage: %s [options] [path]\n"
401 "If not path is given, the current working directory is used.\n"
402 "Options:\n"
403 " -h, --help A short option summary\n"
404 " -u, --unsort Do not sort directory entries\n"
405 " -r, --recursive List subdirectories recursively\n"
406 " -e, --exact-size File sizes will be unformatted (raw bytes count)\n"
407 " -1, --single-column Only the names will be returned\n",
408 cmdname);
409 }
410
411 return;
412}
413
414int cmd_ls(char **argv)
415{
416 unsigned int argc;
417 struct dir_elem_t de;
418 DIR *dirp;
419 int c, opt_ind;
420 int ret = 0;
421 unsigned int scope;
422
423 if (!ls_start(&ls)) {
424 cli_error(CL_EFAIL, "%s: Could not initialize", cmdname);
425 return CMD_FAILURE;
426 }
427
428 argc = cli_count_args(argv);
429
430 c = 0;
431 optreset = 1;
432 optind = 0;
433 opt_ind = 0;
434
435 while (c != -1) {
436 c = getopt_long(argc, argv, "hure1", long_options, &opt_ind);
437 switch (c) {
438 case 'h':
439 help_cmd_ls(HELP_LONG);
440 return CMD_SUCCESS;
441 case 'u':
442 ls.sort = 0;
443 break;
444 case 'r':
445 ls.recursive = 1;
446 break;
447 case 'e':
448 ls.exact_size = true;
449 break;
450 case '1':
451 ls.single_column = true;
452 ls.printer = ls_print_single_column;
453 break;
454 }
455 }
456
457 argc -= optind;
458
459 de.name = (char *) malloc(PATH_MAX);
460 if (!de.name) {
461 cli_error(CL_ENOMEM, "%s: Out of memory", cmdname);
462 return CMD_FAILURE;
463 }
464 memset(de.name, 0, PATH_MAX);
465
466 if (argc == 0) {
467 if (vfs_cwd_get(de.name, PATH_MAX) != EOK) {
468 cli_error(CL_EFAIL, "%s: Failed determining working "
469 "directory", cmdname);
470 return CMD_FAILURE;
471 }
472 } else {
473 str_cpy(de.name, PATH_MAX, argv[optind]);
474 }
475
476 scope = ls_scope(de.name, &de);
477 switch (scope) {
478 case LS_FILE:
479 if (ls.printer(&de) != EOK) {
480 cli_error(CL_ENOMEM, "%s: Out of memory", cmdname);
481 return CMD_FAILURE;
482 }
483 break;
484 case LS_DIR:
485 dirp = opendir(de.name);
486 if (!dirp) {
487 /* May have been deleted between scoping it and opening it */
488 cli_error(CL_EFAIL, "Could not stat %s", de.name);
489 free(de.name);
490 return CMD_FAILURE;
491 }
492 if (ls.recursive)
493 ret = ls_recursive(de.name, dirp);
494 else
495 ret = ls_scan_dir(de.name, dirp, NULL);
496
497 closedir(dirp);
498 break;
499 case LS_BOGUS:
500 return CMD_FAILURE;
501 }
502
503 free(de.name);
504
505 if (ret == -1 || ret == CMD_FAILURE)
506 return CMD_FAILURE;
507 else
508 return CMD_SUCCESS;
509}
Note: See TracBrowser for help on using the repository browser.