ls: implement -q, fix -w0, reduce startup time
No binary (build failed)
—
(log yok)
| @@ -126,6 +126,8 @@ | |
| //usage: "\n -F Append indicator (one of */=@|) to names" | |
| //usage: ) | |
| //usage: "\n -l Long format" | |
| + | ////usage: "\n -g Long format without group column" |
| + | ////TODO: support -G too ("suppress owner column", GNUism) |
| //usage: "\n -i List inode numbers" | |
| //usage: "\n -n List numeric UIDs and GIDs instead of names" | |
| //usage: "\n -s List allocated blocks" | |
| @@ -159,6 +161,8 @@ | |
| //usage: IF_FEATURE_LS_WIDTH( | |
| //usage: "\n -w N Format N columns wide" | |
| //usage: ) | |
| + | ////usage: "\n -Q Double-quote names" |
| + | ////usage: "\n -q Replace unprintable chars with '?'" |
| //usage: IF_FEATURE_LS_COLOR( | |
| //usage: "\n --color[={always,never,auto}]" | |
| //usage: ) | |
| @@ -196,27 +200,47 @@ SPLIT_SUBDIR = 2, | |
| /* -Cadi1l Std options, busybox always supports */ | |
| /* -gnsxA Std options, busybox always supports */ | |
| - | /* -Q GNU option, busybox always supports */ |
| - | /* -k Std option, busybox always supports (by ignoring) */ |
| - | /* It means "for -s, show sizes in kbytes" */ |
| - | /* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */ |
| - | /* since otherwise -s shows kbytes anyway */ |
| + | /* -Q GNU option, busybox always supports: */ |
| + | /* -Q, --quote-name */ |
| + | /* enclose entry names in double quotes */ |
| /* -LHRctur Std options, busybox optionally supports */ | |
| /* -Fp Std options, busybox optionally supports */ | |
| /* -SXvhTw GNU options, busybox optionally supports */ | |
| /* -T WIDTH Ignored (we don't use tabs on output) */ | |
| /* -Z SELinux mandated option, busybox optionally supports */ | |
| + | /* -q Std option, busybox always supports: */ |
| + | /* https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html: */ |
| + | /* Force each instance of non-printable filename characters and */ |
| + | /* <tab> characters to be written as the <question-mark> ('?') */ |
| + | /* character. Implementations may provide this option by default */ |
| + | /* if the output is to a terminal device. */ |
| + | /* -k Std option, busybox always supports (by ignoring) */ |
| + | /* It means "for -s, show sizes in kbytes" */ |
| + | /* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */ |
| + | /* since otherwise -s shows kbytes anyway */ |
| #define ls_options \ | |
| - | "Cadi1lgnsxAk" /* 12 opts, total 12 */ \ |
| - | IF_FEATURE_LS_FILETYPES("Fp") /* 2, 14 */ \ |
| - | IF_FEATURE_LS_RECURSIVE("R") /* 1, 15 */ \ |
| - | IF_SELINUX("Z") /* 1, 16 */ \ |
| - | "Q" /* 1, 17 */ \ |
| - | IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 20 */ \ |
| - | IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 24 */ \ |
| - | IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 26 */ \ |
| - | IF_FEATURE_HUMAN_READABLE("h") /* 1, 27 */ \ |
| - | IF_FEATURE_LS_WIDTH("T:w:") /* 2, 29 */ |
| + | "Cadi1lgnsxA" /* 11 opts, total 11 */ \ |
| + | IF_FEATURE_LS_FILETYPES("Fp") /* 2, 13 */ \ |
| + | IF_FEATURE_LS_RECURSIVE("R") /* 1, 14 */ \ |
| + | IF_SELINUX("Z") /* 1, 15 */ \ |
| + | "Q" /* 1, 16 */ \ |
| + | IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 19 */ \ |
| + | IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 23 */ \ |
| + | IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 25 */ \ |
| + | IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */ \ |
| + | IF_FEATURE_LS_WIDTH("T:w:") /* 2, 28 */ \ |
| + | IF_LONG_OPTS("\xff") /* 1, 29 */ \ |
| + | IF_LONG_OPTS("\xfe") /* 1, 30 */ \ |
| + | IF_LONG_OPTS("\xfd") /* 1, 31 */ \ |
| + | "qk" /* 2, 33 */ |
| + | |
| + | #if ENABLE_LONG_OPTS |
| + | static const char ls_longopts[] ALIGN1 = |
| + | "full-time\0" No_argument "\xff" |
| + | "group-directories-first\0" No_argument "\xfe" |
| + | IF_FEATURE_LS_COLOR("color\0" Optional_argument "\xfd") |
| + | ; |
| + | #endif |
| enum { | |
| OPT_C = (1 << 0), | |
| @@ -230,29 +254,31 @@ enum { | |
| OPT_s = (1 << 8), | |
| OPT_x = (1 << 9), | |
| OPT_A = (1 << 10), | |
| - | //OPT_k = (1 << 11), |
| - | OPTBIT_F = 12, |
| - | OPTBIT_p, /* 13 */ |
| + | OPTBIT_F = 11, |
| + | OPTBIT_p, /* 12 */ |
| OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES, | |
| OPTBIT_Z = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE, | |
| OPTBIT_Q = OPTBIT_Z + 1 * ENABLE_SELINUX, | |
| - | OPTBIT_c, /* 17 */ |
| - | OPTBIT_t, /* 18 */ |
| - | OPTBIT_u, /* 19 */ |
| + | OPTBIT_c, /* 16 */ |
| + | OPTBIT_t, /* 17 */ |
| + | OPTBIT_u, /* 18 */ |
| OPTBIT_S = OPTBIT_c + 3 * ENABLE_FEATURE_LS_TIMESTAMPS, | |
| - | OPTBIT_X, /* 21 */ |
| - | OPTBIT_r, /* 22 */ |
| - | OPTBIT_v, /* 23 */ |
| + | OPTBIT_X, /* 20 */ |
| + | OPTBIT_r, /* 21 */ |
| + | OPTBIT_v, /* 22 */ |
| OPTBIT_L = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES, | |
| - | OPTBIT_H, /* 25 */ |
| + | OPTBIT_H, /* 24 */ |
| OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS, | |
| OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE, | |
| - | OPTBIT_w, /* 28 */ |
| + | OPTBIT_w, /* 27 */ |
| OPTBIT_full_time = OPTBIT_T + 2 * ENABLE_FEATURE_LS_WIDTH, | |
| OPTBIT_dirs_first, | |
| - | OPTBIT_color, /* 31 */ |
| - | /* with long opts, we use all 32 bits */ |
| + | OPTBIT_color, /* 30 */ |
| + | OPTBIT_q = OPTBIT_color + 1, /* 31 */ |
| + | OPTBIT_k = OPTBIT_q + 1, /* 32 */ |
| + | /* with all options enabled, we use all 32 bits and even one extra bit! */ |
| + | /* this works because -k is ignored, and getopt32 allows such "ignore" options past 31th bit */ |
| OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES, | |
| OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES, | |
| @@ -274,6 +300,8 @@ enum { | |
| OPT_full_time = (1 << OPTBIT_full_time ) * ENABLE_LONG_OPTS, | |
| OPT_dirs_first = (1 << OPTBIT_dirs_first) * ENABLE_LONG_OPTS, | |
| OPT_color = (1 << OPTBIT_color ) * ENABLE_FEATURE_LS_COLOR, | |
| + | OPT_q = (1 << OPTBIT_q), |
| + | //-k is ignored: OPT_k = (1 << OPTBIT_k), |
| }; | |
| /* | |
| @@ -327,6 +355,7 @@ struct globals { | |
| #endif | |
| smallint exit_code; | |
| smallint show_dirname; | |
| + | smallint tty_out; |
| #if ENABLE_FEATURE_LS_WIDTH | |
| unsigned terminal_width; | |
| # define G_terminal_width (G.terminal_width) | |
| @@ -343,16 +372,21 @@ struct globals { | |
| setup_common_bufsiz(); \ | |
| /* we have to zero it out because of NOEXEC */ \ | |
| memset(&G, 0, sizeof(G)); \ | |
| - | IF_FEATURE_LS_WIDTH(G_terminal_width = TERMINAL_WIDTH;) \ |
| + | IF_FEATURE_LS_WIDTH(G_terminal_width = ~0U;) \ |
| IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \ | |
| } while (0) | |
| #define ESC "\033" | |
| + | static int G_isatty(void) |
| + | { |
| + | if (!G.tty_out) /* not known yet? */ |
| + | G.tty_out = isatty(STDOUT_FILENO) + 1; |
| + | return (G.tty_out == 2); |
| + | } |
| /*** Output code ***/ | |
| - | |
| /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket | |
| * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file | |
| * 3/7:multiplexed char/block device) | |
| @@ -420,6 +454,9 @@ static unsigned calc_name_len(const char *name) | |
| unsigned len; | |
| uni_stat_t uni_stat; | |
| + | if (!(option_mask32 & OPT_q)) |
| + | return strlen(name); |
| + | |
| // TODO: quote tab as \t, etc, if -Q | |
| name = printable_string2(&uni_stat, name); | |
| @@ -449,6 +486,11 @@ static unsigned print_name(const char *name) | |
| unsigned len; | |
| uni_stat_t uni_stat; | |
| + | if (!(option_mask32 & OPT_q)) { |
| + | fputs_stdout(name); |
| + | return strlen(name); |
| + | } |
| + | |
| // TODO: quote tab as \t, etc, if -Q | |
| name = printable_string2(&uni_stat, name); | |
| @@ -646,7 +688,7 @@ static void display_files(struct dnode **dn, unsigned nfiles) | |
| unsigned i, ncols, nrows, row, nc; | |
| unsigned column; | |
| unsigned nexttab; | |
| - | unsigned column_width = 0; /* used only by coulmnal output */ |
| + | unsigned column_width = 0; /* used only by columnar output */ |
| if (option_mask32 & (OPT_l|OPT_1)) { | |
| ncols = 1; | |
| @@ -691,6 +733,11 @@ static void display_files(struct dnode **dn, unsigned nfiles) | |
| } | |
| nexttab = column + column_width; | |
| column += display_single(dn[i]); | |
| + | } else { |
| + | /* if -w999999999, ncols can be very large */ |
| + | //bb_error_msg(" col:%u ncol:%u i:%i", nc, ncols, i); sleep1(); |
| + | /* without "break", we loop millions of times here */ |
| + | break; |
| } | |
| } | |
| putchar('\n'); | |
| @@ -1090,25 +1137,11 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| /* need to initialize since --color has _an optional_ argument */ | |
| const char *color_opt = color_str; /* "always" */ | |
| #endif | |
| - | #if ENABLE_LONG_OPTS |
| - | static const char ls_longopts[] ALIGN1 = |
| - | "full-time\0" No_argument "\xff" |
| - | "group-directories-first\0" No_argument "\xfe" |
| - | IF_FEATURE_LS_COLOR("color\0" Optional_argument "\xfd") |
| - | ; |
| - | #endif |
| INIT_G(); | |
| init_unicode(); | |
| - | #if ENABLE_FEATURE_LS_WIDTH |
| - | /* obtain the terminal width */ |
| - | G_terminal_width = get_terminal_width(STDIN_FILENO); |
| - | /* go one less... */ |
| - | G_terminal_width--; |
| - | #endif |
| - | |
| /* process options */ | |
| opt = getopt32long(argv, "^" | |
| ls_options | |
| @@ -1144,6 +1177,17 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| exit(0); | |
| #endif | |
| + | #if ENABLE_FEATURE_LS_WIDTH |
| + | if ((int)G_terminal_width < 0) { |
| + | /* obtain the terminal width */ |
| + | G_terminal_width = get_terminal_width(STDIN_FILENO); |
| + | /* go one less... */ |
| + | G_terminal_width--; |
| + | } |
| + | if (G_terminal_width == 0) /* -w0 */ |
| + | G_terminal_width = INT_MAX; /* "infinite" */ |
| + | #endif |
| + | |
| #if ENABLE_SELINUX | |
| if (opt & OPT_Z) { | |
| if (!is_selinux_enabled()) | |
| @@ -1157,7 +1201,7 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| char *p = getenv("LS_COLORS"); | |
| /* LS_COLORS is unset, or (not empty && not "none") ? */ | |
| if (!p || (p[0] && strcmp(p, "none") != 0)) { | |
| - | if (isatty(STDOUT_FILENO)) { |
| + | if (G_isatty()) { |
| /* check isatty() last because it's expensive (syscall) */ | |
| G_show_color = 1; | |
| } | |
| @@ -1166,15 +1210,19 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| if (opt & OPT_color) { | |
| if (color_opt[0] == 'n') | |
| G_show_color = 0; | |
| - | else switch (index_in_substrings(color_str, color_opt)) { |
| - | case 3: |
| - | case 4: |
| - | case 5: |
| - | if (!is_TERM_dumb() && isatty(STDOUT_FILENO)) { |
| - | case 0: |
| - | case 1: |
| - | case 2: |
| - | G_show_color = 1; |
| + | else if (!G_show_color) { |
| + | /* if() is not needed, but avoids extra isatty() if G_show_color is already set */ |
| + | /* Check --color=COLOR_OPT and maybe set show_color=1 */ |
| + | switch (index_in_substrings(color_str, color_opt)) { |
| + | case 3: // auto |
| + | case 4: // tty |
| + | case 5: // if-tty |
| + | if (!is_TERM_dumb() && G_isatty()) { |
| + | case 0: // always |
| + | case 1: // yes |
| + | case 2: // force |
| + | G_show_color = 1; |
| + | } |
| } | |
| } | |
| } | |
| @@ -1182,7 +1230,7 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| /* sort out which command line options take precedence */ | |
| if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d)) | |
| - | option_mask32 &= ~OPT_R; /* no recurse if listing only dir */ |
| + | opt = option_mask32 &= ~OPT_R; /* no recurse if listing only dir */ |
| if (!(opt & OPT_l)) { /* not -l? */ | |
| if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { | |
| /* when to sort by time? -t[cu] sorts by time even with -l */ | |
| @@ -1190,18 +1238,21 @@ int ls_main(int argc UNUSED_PARAM, char **argv) | |
| /* without -l, bare -c or -u enable sort too */ | |
| /* (with -l, bare -c or -u just select which time to show) */ | |
| if (opt & (OPT_c|OPT_u)) { | |
| - | option_mask32 |= OPT_t; |
| + | opt = option_mask32 |= OPT_t; |
| } | |
| } | |
| } | |
| /* choose a display format if one was not already specified by an option */ | |
| - | if (!(option_mask32 & (OPT_l|OPT_1|OPT_x|OPT_C))) |
| - | option_mask32 |= (isatty(STDOUT_FILENO) ? OPT_C : OPT_1); |
| + | if (!(opt & (OPT_l|OPT_1|OPT_x|OPT_C))) |
| + | opt = option_mask32 |= (G_isatty() ? OPT_C : OPT_1); |
| + | |
| + | if (!(opt & OPT_q) && G_isatty()) |
| + | opt = option_mask32 |= OPT_q; |
| if (ENABLE_FTPD && applet_name[0] == 'f') { | |
| /* ftpd secret backdoor. dirs first are much nicer */ | |
| - | option_mask32 |= OPT_dirs_first; |
| + | opt = option_mask32 |= OPT_dirs_first; |
| } | |
| argv += optind; | |
| @@ -530,6 +530,7 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, | |
| * "fake" short options, like this one: | |
| * wget $'-\203' "Test: test" http://kernel.org/ | |
| * (supposed to act as --header, but doesn't) */ | |
| + | next_opt: |
| #if ENABLE_LONG_OPTS | |
| while ((c = getopt_long(argc, argv, applet_opts, | |
| long_options, NULL)) != -1) { | |
| @@ -544,8 +545,16 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, | |
| * but we construct long opts so that flag | |
| * is always NULL (see above) */ | |
| if (on_off->opt_char == '\0' /* && c != '\0' */) { | |
| - | /* c is probably '?' - "bad option" */ |
| - | goto error; |
| + | /* We reached the end of complementary[] and did not find -c */ |
| + | if (c == '?') /* getopt says: "bad option, or option has no required argument" */ |
| + | goto error; |
| + | /* if there were options beyond 32 bits (example: ls), |
| + | * they got no complementary[] slot, and no result bit. |
| + | * IOW: they must be "accept but ignore" options. |
| + | * For them, we end up here. |
| + | */ |
| + | //bb_error_msg("ignored option '%c', skipping", c); |
| + | goto next_opt; |
| } | |
| } | |
| if (flags & on_off->incongruously) | |