diff options
| author | Denis Vlasenko <vda.linux@googlemail.com> | 2009-04-18 01:23:21 +0000 |
|---|---|---|
| committer | Denis Vlasenko <vda.linux@googlemail.com> | 2009-04-18 01:23:21 +0000 |
| commit | 6b9e05392b4457f0ea808810761f8fb9efdffc44 (patch) | |
| tree | 22f987ef8ae40bcee3cfb2b54dd19afbd1dc9e07 /shell | |
| parent | 7b9e5c57ae388be642234ee89c75b733711f905d (diff) | |
| download | busybox-w32-6b9e05392b4457f0ea808810761f8fb9efdffc44.tar.gz busybox-w32-6b9e05392b4457f0ea808810761f8fb9efdffc44.tar.bz2 busybox-w32-6b9e05392b4457f0ea808810761f8fb9efdffc44.zip | |
hush: implement proper SIGHUP handling
function old new delta
check_and_run_traps 164 229 +65
insert_bg_job 376 366 -10
hush_main 937 927 -10
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/hush.c | 203 |
1 files changed, 108 insertions, 95 deletions
diff --git a/shell/hush.c b/shell/hush.c index f9757a723..72589fd7f 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -52,11 +52,10 @@ | |||
| 52 | * grep for "TODO" and fix (some of them are easy) | 52 | * grep for "TODO" and fix (some of them are easy) |
| 53 | * change { and } from special chars to reserved words | 53 | * change { and } from special chars to reserved words |
| 54 | * $var refs in function do not pick up values set by "var=val func" | 54 | * $var refs in function do not pick up values set by "var=val func" |
| 55 | * builtins: return, ulimit | 55 | * builtins: ulimit |
| 56 | * follow IFS rules more precisely, including update semantics | 56 | * follow IFS rules more precisely, including update semantics |
| 57 | * figure out what to do with backslash-newline | 57 | * figure out what to do with backslash-newline |
| 58 | * continuation lines, both explicit and implicit - done? | 58 | * continuation lines, both explicit and implicit - done? |
| 59 | * SIGHUP handling | ||
| 60 | * separate job control from interactiveness | 59 | * separate job control from interactiveness |
| 61 | * (testcase: booting with init=/bin/hush does not show prompt (2009-04)) | 60 | * (testcase: booting with init=/bin/hush does not show prompt (2009-04)) |
| 62 | * | 61 | * |
| @@ -1070,8 +1069,9 @@ static void restore_G_args(save_arg_t *sv, char **argv) | |||
| 1070 | * "trap 'cmd' SIGxxx": | 1069 | * "trap 'cmd' SIGxxx": |
| 1071 | * set bit in blocked_set (even if 'cmd' is '') | 1070 | * set bit in blocked_set (even if 'cmd' is '') |
| 1072 | * after [v]fork, if we plan to be a shell: | 1071 | * after [v]fork, if we plan to be a shell: |
| 1073 | * nothing for {} child shell (say, "true | { true; true; } | true") | 1072 | * unblock signals with special interactive handling |
| 1074 | * unset all traps if () shell. | 1073 | * (child shell is not interactive), |
| 1074 | * unset all traps (note: regardless of child shell's type - {}, (), etc) | ||
| 1075 | * after [v]fork, if we plan to exec: | 1075 | * after [v]fork, if we plan to exec: |
| 1076 | * POSIX says pending signal mask is cleared in child - no need to clear it. | 1076 | * POSIX says pending signal mask is cleared in child - no need to clear it. |
| 1077 | * Restore blocked signal set to one inherited by shell just prior to exec. | 1077 | * Restore blocked signal set to one inherited by shell just prior to exec. |
| @@ -1084,9 +1084,9 @@ enum { | |||
| 1084 | SPECIAL_INTERACTIVE_SIGS = 0 | 1084 | SPECIAL_INTERACTIVE_SIGS = 0 |
| 1085 | #if ENABLE_HUSH_JOB | 1085 | #if ENABLE_HUSH_JOB |
| 1086 | | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) | 1086 | | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) |
| 1087 | | (1 << SIGHUP) | ||
| 1087 | #endif | 1088 | #endif |
| 1088 | | (1 << SIGTERM) | 1089 | | (1 << SIGTERM) |
| 1089 | //TODO | (1 << SIGHUP) | ||
| 1090 | | (1 << SIGINT) | 1090 | | (1 << SIGINT) |
| 1091 | }; | 1091 | }; |
| 1092 | 1092 | ||
| @@ -1095,53 +1095,6 @@ enum { | |||
| 1095 | // G.count_SIGCHLD++; | 1095 | // G.count_SIGCHLD++; |
| 1096 | //} | 1096 | //} |
| 1097 | 1097 | ||
| 1098 | static int check_and_run_traps(int sig) | ||
| 1099 | { | ||
| 1100 | static const struct timespec zero_timespec = { 0, 0 }; | ||
| 1101 | smalluint save_rcode; | ||
| 1102 | int last_sig = 0; | ||
| 1103 | |||
| 1104 | if (sig) | ||
| 1105 | goto jump_in; | ||
| 1106 | while (1) { | ||
| 1107 | sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); | ||
| 1108 | if (sig <= 0) | ||
| 1109 | break; | ||
| 1110 | jump_in: | ||
| 1111 | last_sig = sig; | ||
| 1112 | if (G.traps && G.traps[sig]) { | ||
| 1113 | if (G.traps[sig][0]) { | ||
| 1114 | /* We have user-defined handler */ | ||
| 1115 | char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL }; | ||
| 1116 | save_rcode = G.last_exitcode; | ||
| 1117 | builtin_eval(argv); | ||
| 1118 | free(argv[1]); | ||
| 1119 | G.last_exitcode = save_rcode; | ||
| 1120 | } /* else: "" trap, ignoring signal */ | ||
| 1121 | continue; | ||
| 1122 | } | ||
| 1123 | /* not a trap: special action */ | ||
| 1124 | switch (sig) { | ||
| 1125 | // case SIGCHLD: | ||
| 1126 | // G.count_SIGCHLD++; | ||
| 1127 | // break; | ||
| 1128 | case SIGINT: | ||
| 1129 | //TODO: add putchar('\n') also when we detect that child was killed (sleep 5 + ^C) | ||
| 1130 | /* Builtin was ^C'ed, make it look prettier: */ | ||
| 1131 | bb_putchar('\n'); | ||
| 1132 | G.flag_SIGINT = 1; | ||
| 1133 | break; | ||
| 1134 | //TODO | ||
| 1135 | // case SIGHUP: ... | ||
| 1136 | // break; | ||
| 1137 | default: /* ignored: */ | ||
| 1138 | /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ | ||
| 1139 | break; | ||
| 1140 | } | ||
| 1141 | } | ||
| 1142 | return last_sig; | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | #if ENABLE_HUSH_JOB | 1098 | #if ENABLE_HUSH_JOB |
| 1146 | 1099 | ||
| 1147 | /* After [v]fork, in child: do not restore tty pgrp on xfunc death */ | 1100 | /* After [v]fork, in child: do not restore tty pgrp on xfunc death */ |
| @@ -1151,7 +1104,7 @@ static int check_and_run_traps(int sig) | |||
| 1151 | 1104 | ||
| 1152 | /* Restores tty foreground process group, and exits. | 1105 | /* Restores tty foreground process group, and exits. |
| 1153 | * May be called as signal handler for fatal signal | 1106 | * May be called as signal handler for fatal signal |
| 1154 | * (will faithfully resend signal to itself, producing correct exit state) | 1107 | * (will resend signal to itself, producing correct exit state) |
| 1155 | * or called directly with -EXITCODE. | 1108 | * or called directly with -EXITCODE. |
| 1156 | * We also call it if xfunc is exiting. */ | 1109 | * We also call it if xfunc is exiting. */ |
| 1157 | static void sigexit(int sig) NORETURN; | 1110 | static void sigexit(int sig) NORETURN; |
| @@ -1201,6 +1154,65 @@ static void hush_exit(int exitcode) | |||
| 1201 | #endif | 1154 | #endif |
| 1202 | } | 1155 | } |
| 1203 | 1156 | ||
| 1157 | static int check_and_run_traps(int sig) | ||
| 1158 | { | ||
| 1159 | static const struct timespec zero_timespec = { 0, 0 }; | ||
| 1160 | smalluint save_rcode; | ||
| 1161 | int last_sig = 0; | ||
| 1162 | |||
| 1163 | if (sig) | ||
| 1164 | goto jump_in; | ||
| 1165 | while (1) { | ||
| 1166 | sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); | ||
| 1167 | if (sig <= 0) | ||
| 1168 | break; | ||
| 1169 | jump_in: | ||
| 1170 | last_sig = sig; | ||
| 1171 | if (G.traps && G.traps[sig]) { | ||
| 1172 | if (G.traps[sig][0]) { | ||
| 1173 | /* We have user-defined handler */ | ||
| 1174 | char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL }; | ||
| 1175 | save_rcode = G.last_exitcode; | ||
| 1176 | builtin_eval(argv); | ||
| 1177 | free(argv[1]); | ||
| 1178 | G.last_exitcode = save_rcode; | ||
| 1179 | } /* else: "" trap, ignoring signal */ | ||
| 1180 | continue; | ||
| 1181 | } | ||
| 1182 | /* not a trap: special action */ | ||
| 1183 | switch (sig) { | ||
| 1184 | // case SIGCHLD: | ||
| 1185 | // G.count_SIGCHLD++; | ||
| 1186 | // break; | ||
| 1187 | case SIGINT: | ||
| 1188 | //TODO: add putchar('\n') also when we detect that child was killed (sleep 5 + ^C) | ||
| 1189 | /* Builtin was ^C'ed, make it look prettier: */ | ||
| 1190 | bb_putchar('\n'); | ||
| 1191 | G.flag_SIGINT = 1; | ||
| 1192 | break; | ||
| 1193 | #if ENABLE_HUSH_JOB | ||
| 1194 | case SIGHUP: { | ||
| 1195 | struct pipe *job; | ||
| 1196 | /* bash is observed to signal whole process groups, | ||
| 1197 | * not individual processes */ | ||
| 1198 | for (job = G.job_list; job; job = job->next) { | ||
| 1199 | if (job->pgrp <= 0) | ||
| 1200 | continue; | ||
| 1201 | debug_printf_exec("HUPing pgrp %d\n", job->pgrp); | ||
| 1202 | if (kill(- job->pgrp, SIGHUP) == 0) | ||
| 1203 | kill(- job->pgrp, SIGCONT); | ||
| 1204 | } | ||
| 1205 | sigexit(SIGHUP); | ||
| 1206 | } | ||
| 1207 | #endif | ||
| 1208 | default: /* ignored: */ | ||
| 1209 | /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ | ||
| 1210 | break; | ||
| 1211 | } | ||
| 1212 | } | ||
| 1213 | return last_sig; | ||
| 1214 | } | ||
| 1215 | |||
| 1204 | 1216 | ||
| 1205 | static const char *set_cwd(void) | 1217 | static const char *set_cwd(void) |
| 1206 | { | 1218 | { |
| @@ -2061,7 +2073,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) | |||
| 2061 | case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */ | 2073 | case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */ |
| 2062 | *p = '\0'; | 2074 | *p = '\0'; |
| 2063 | arg++; | 2075 | arg++; |
| 2064 | //TODO: can we just stuff it into "output" directly? | 2076 | /* Can't just stuff it into output o_string, |
| 2077 | * expanded result may need to be globbed | ||
| 2078 | * and $IFS-splitted */ | ||
| 2065 | debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); | 2079 | debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); |
| 2066 | process_command_subs(&subst_result, arg); | 2080 | process_command_subs(&subst_result, arg); |
| 2067 | debug_printf_subst("SUBST RES '%s'\n", subst_result.data); | 2081 | debug_printf_subst("SUBST RES '%s'\n", subst_result.data); |
| @@ -2777,7 +2791,8 @@ static const struct function *find_function(const char *name) | |||
| 2777 | } | 2791 | } |
| 2778 | funcp = funcp->next; | 2792 | funcp = funcp->next; |
| 2779 | } | 2793 | } |
| 2780 | debug_printf_exec("found function '%s'\n", name); | 2794 | if (funcp) |
| 2795 | debug_printf_exec("found function '%s'\n", name); | ||
| 2781 | return funcp; | 2796 | return funcp; |
| 2782 | } | 2797 | } |
| 2783 | 2798 | ||
| @@ -2819,7 +2834,7 @@ static struct function *new_function(char *name) | |||
| 2819 | } | 2834 | } |
| 2820 | goto skip; | 2835 | goto skip; |
| 2821 | } | 2836 | } |
| 2822 | debug_printf_exec("remembering new function '%s'\n", command->argv[0]); | 2837 | debug_printf_exec("remembering new function '%s'\n", name); |
| 2823 | funcp = *funcpp = xzalloc(sizeof(*funcp)); | 2838 | funcp = *funcpp = xzalloc(sizeof(*funcp)); |
| 2824 | /*funcp->next = NULL;*/ | 2839 | /*funcp->next = NULL;*/ |
| 2825 | skip: | 2840 | skip: |
| @@ -3059,8 +3074,11 @@ static const char *get_cmdtext(struct pipe *pi) | |||
| 3059 | } | 3074 | } |
| 3060 | 3075 | ||
| 3061 | len = 0; | 3076 | len = 0; |
| 3062 | do len += strlen(*argv) + 1; while (*++argv); | 3077 | do { |
| 3063 | pi->cmdtext = p = xmalloc(len); | 3078 | len += strlen(*argv) + 1; |
| 3079 | } while (*++argv); | ||
| 3080 | p = xmalloc(len); | ||
| 3081 | pi->cmdtext = p;// = xmalloc(len); | ||
| 3064 | argv = pi->cmds[0].argv; | 3082 | argv = pi->cmds[0].argv; |
| 3065 | do { | 3083 | do { |
| 3066 | len = strlen(*argv); | 3084 | len = strlen(*argv); |
| @@ -3074,44 +3092,36 @@ static const char *get_cmdtext(struct pipe *pi) | |||
| 3074 | 3092 | ||
| 3075 | static void insert_bg_job(struct pipe *pi) | 3093 | static void insert_bg_job(struct pipe *pi) |
| 3076 | { | 3094 | { |
| 3077 | struct pipe *thejob; | 3095 | struct pipe *job, **jobp; |
| 3078 | int i; | 3096 | int i; |
| 3079 | 3097 | ||
| 3080 | /* Linear search for the ID of the job to use */ | 3098 | /* Linear search for the ID of the job to use */ |
| 3081 | pi->jobid = 1; | 3099 | pi->jobid = 1; |
| 3082 | for (thejob = G.job_list; thejob; thejob = thejob->next) | 3100 | for (job = G.job_list; job; job = job->next) |
| 3083 | if (thejob->jobid >= pi->jobid) | 3101 | if (job->jobid >= pi->jobid) |
| 3084 | pi->jobid = thejob->jobid + 1; | 3102 | pi->jobid = job->jobid + 1; |
| 3085 | 3103 | ||
| 3086 | /* Add thejob to the list of running jobs */ | 3104 | /* Add job to the list of running jobs */ |
| 3087 | if (!G.job_list) { | 3105 | jobp = &G.job_list; |
| 3088 | thejob = G.job_list = xmalloc(sizeof(*thejob)); | 3106 | while ((job = *jobp) != NULL) |
| 3089 | } else { | 3107 | jobp = &job->next; |
| 3090 | for (thejob = G.job_list; thejob->next; thejob = thejob->next) | 3108 | job = *jobp = xmalloc(sizeof(*job)); |
| 3091 | continue; | 3109 | |
| 3092 | thejob->next = xmalloc(sizeof(*thejob)); | 3110 | *job = *pi; /* physical copy */ |
| 3093 | thejob = thejob->next; | 3111 | job->next = NULL; |
| 3094 | } | 3112 | job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); |
| 3095 | 3113 | /* Cannot copy entire pi->cmds[] vector! This causes double frees */ | |
| 3096 | /* Physically copy the struct job */ | ||
| 3097 | memcpy(thejob, pi, sizeof(struct pipe)); | ||
| 3098 | thejob->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); | ||
| 3099 | /* We cannot copy entire pi->cmds[] vector! Double free()s will happen */ | ||
| 3100 | for (i = 0; i < pi->num_cmds; i++) { | 3114 | for (i = 0; i < pi->num_cmds; i++) { |
| 3101 | // TODO: do we really need to have so many fields which are just dead weight | 3115 | job->cmds[i].pid = pi->cmds[i].pid; |
| 3102 | // at execution stage? | ||
| 3103 | thejob->cmds[i].pid = pi->cmds[i].pid; | ||
| 3104 | /* all other fields are not used and stay zero */ | 3116 | /* all other fields are not used and stay zero */ |
| 3105 | } | 3117 | } |
| 3106 | thejob->next = NULL; | 3118 | job->cmdtext = xstrdup(get_cmdtext(pi)); |
| 3107 | thejob->cmdtext = xstrdup(get_cmdtext(pi)); | ||
| 3108 | 3119 | ||
| 3109 | /* We don't wait for background thejobs to return -- append it | ||
| 3110 | to the list of backgrounded thejobs and leave it alone */ | ||
| 3111 | if (G_interactive_fd) | 3120 | if (G_interactive_fd) |
| 3112 | printf("[%d] %d %s\n", thejob->jobid, thejob->cmds[0].pid, thejob->cmdtext); | 3121 | printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext); |
| 3113 | G.last_bg_pid = thejob->cmds[0].pid; | 3122 | /* Last command's pid goes to $! */ |
| 3114 | G.last_jobid = thejob->jobid; | 3123 | G.last_bg_pid = job->cmds[job->num_cmds - 1].pid; |
| 3124 | G.last_jobid = job->jobid; | ||
| 3115 | } | 3125 | } |
| 3116 | 3126 | ||
| 3117 | static void remove_bg_job(struct pipe *pi) | 3127 | static void remove_bg_job(struct pipe *pi) |
| @@ -3306,7 +3316,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) | |||
| 3306 | * cmd || ... { list } || ... | 3316 | * cmd || ... { list } || ... |
| 3307 | * If it is, then we can run cmd as a builtin, NOFORK [do we do this?], | 3317 | * If it is, then we can run cmd as a builtin, NOFORK [do we do this?], |
| 3308 | * or (if SH_STANDALONE) an applet, and we can run the { list } | 3318 | * or (if SH_STANDALONE) an applet, and we can run the { list } |
| 3309 | * with run_list(). If it isn't one of these, we fork and exec cmd. | 3319 | * with run_list. If it isn't one of these, we fork and exec cmd. |
| 3310 | * | 3320 | * |
| 3311 | * Cases when we must fork: | 3321 | * Cases when we must fork: |
| 3312 | * non-single: cmd | cmd | 3322 | * non-single: cmd | cmd |
| @@ -3942,7 +3952,11 @@ static int run_list(struct pipe *pi) | |||
| 3942 | check_and_run_traps(0); | 3952 | check_and_run_traps(0); |
| 3943 | #if ENABLE_HUSH_JOB | 3953 | #if ENABLE_HUSH_JOB |
| 3944 | if (G.run_list_level == 1) | 3954 | if (G.run_list_level == 1) |
| 3955 | { | ||
| 3956 | debug_printf_exec("insert_bg_job1\n"); | ||
| 3945 | insert_bg_job(pi); | 3957 | insert_bg_job(pi); |
| 3958 | debug_printf_exec("insert_bg_job2\n"); | ||
| 3959 | } | ||
| 3946 | #endif | 3960 | #endif |
| 3947 | G.last_exitcode = rcode = EXIT_SUCCESS; | 3961 | G.last_exitcode = rcode = EXIT_SUCCESS; |
| 3948 | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); | 3962 | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); |
| @@ -5853,11 +5867,10 @@ static void set_fatal_handlers(void) | |||
| 5853 | /* bash 3.2 seems to handle these just like 'fatal' ones */ | 5867 | /* bash 3.2 seems to handle these just like 'fatal' ones */ |
| 5854 | maybe_set_to_sigexit(SIGPIPE); | 5868 | maybe_set_to_sigexit(SIGPIPE); |
| 5855 | maybe_set_to_sigexit(SIGALRM); | 5869 | maybe_set_to_sigexit(SIGALRM); |
| 5856 | //TODO: disable and move down when proper SIGHUP handling is added | 5870 | /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked. |
| 5857 | maybe_set_to_sigexit(SIGHUP ); | ||
| 5858 | /* if we are interactive, [SIGHUP,] SIGTERM and SIGINT are masked. | ||
| 5859 | * if we aren't interactive... but in this case | 5871 | * if we aren't interactive... but in this case |
| 5860 | * we never want to restore pgrp on exit, and this fn is not called */ | 5872 | * we never want to restore pgrp on exit, and this fn is not called */ |
| 5873 | /*maybe_set_to_sigexit(SIGHUP );*/ | ||
| 5861 | /*maybe_set_to_sigexit(SIGTERM);*/ | 5874 | /*maybe_set_to_sigexit(SIGTERM);*/ |
| 5862 | /*maybe_set_to_sigexit(SIGINT );*/ | 5875 | /*maybe_set_to_sigexit(SIGINT );*/ |
| 5863 | } | 5876 | } |
| @@ -6350,7 +6363,7 @@ static int builtin_exec(char **argv) | |||
| 6350 | #if !BB_MMU | 6363 | #if !BB_MMU |
| 6351 | nommu_save_t dummy; | 6364 | nommu_save_t dummy; |
| 6352 | #endif | 6365 | #endif |
| 6353 | // TODO: if exec fails, bash does NOT exit! We do... | 6366 | /* TODO: if exec fails, bash does NOT exit! We do... */ |
| 6354 | pseudo_exec_argv(&dummy, argv, 0, NULL); | 6367 | pseudo_exec_argv(&dummy, argv, 0, NULL); |
| 6355 | /* never returns */ | 6368 | /* never returns */ |
| 6356 | } | 6369 | } |
| @@ -6472,8 +6485,8 @@ static int builtin_fg_bg(char **argv) | |||
| 6472 | bb_error_msg("%s: %d: no such job", argv[0], jobnum); | 6485 | bb_error_msg("%s: %d: no such job", argv[0], jobnum); |
| 6473 | return EXIT_FAILURE; | 6486 | return EXIT_FAILURE; |
| 6474 | found: | 6487 | found: |
| 6475 | // TODO: bash prints a string representation | 6488 | /* TODO: bash prints a string representation |
| 6476 | // of job being foregrounded (like "sleep 1 | cat") | 6489 | * of job being foregrounded (like "sleep 1 | cat") */ |
| 6477 | if (argv[0][0] == 'f') { | 6490 | if (argv[0][0] == 'f') { |
| 6478 | /* Put the job into the foreground. */ | 6491 | /* Put the job into the foreground. */ |
| 6479 | tcsetpgrp(G_interactive_fd, pi->pgrp); | 6492 | tcsetpgrp(G_interactive_fd, pi->pgrp); |
| @@ -6705,7 +6718,7 @@ static int builtin_source(char **argv) | |||
| 6705 | if (*++argv == NULL) | 6718 | if (*++argv == NULL) |
| 6706 | return EXIT_FAILURE; | 6719 | return EXIT_FAILURE; |
| 6707 | 6720 | ||
| 6708 | /* TODO: search through $PATH is missing */ | 6721 | // TODO: search through $PATH is missing |
| 6709 | input = fopen_or_warn(*argv, "r"); | 6722 | input = fopen_or_warn(*argv, "r"); |
| 6710 | if (!input) { | 6723 | if (!input) { |
| 6711 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ | 6724 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ |
