diff options
author | Ron Yorston <rmy@pobox.com> | 2014-05-06 20:41:10 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2014-05-06 20:41:10 +0100 |
commit | d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e (patch) | |
tree | 4b364ba4b6b9e96c2629fe382fef0248d76833dd | |
parent | 7905d97aeece18da362a5a1e066abff2d2e5c16b (diff) | |
parent | d257608a8429b64e1a04c7cb6d99975eeb2c3955 (diff) | |
download | busybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.tar.gz busybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.tar.bz2 busybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.zip |
Merge branch 'busybox' into merge
Conflicts:
debianutils/which.c
editors/vi.c
libbb/executable.c
44 files changed, 1697 insertions, 783 deletions
diff --git a/coreutils/test.c b/coreutils/test.c index d8af4dcf3..139f1db75 100644 --- a/coreutils/test.c +++ b/coreutils/test.c | |||
@@ -650,7 +650,7 @@ static int filstat(char *nm, enum token mode) | |||
650 | if (mode == FILEX) { | 650 | if (mode == FILEX) { |
651 | char *p; | 651 | char *p; |
652 | 652 | ||
653 | if (execable_file(nm)) { | 653 | if (file_is_executable(nm)) { |
654 | return 1; | 654 | return 1; |
655 | } | 655 | } |
656 | else if ((p=win32_execable_file(nm))) { | 656 | else if ((p=win32_execable_file(nm))) { |
diff --git a/debianutils/which.c b/debianutils/which.c index bcca95331..fe6cf2f78 100644 --- a/debianutils/which.c +++ b/debianutils/which.c | |||
@@ -1,13 +1,9 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | 1 | /* vi: set sw=4 ts=4: */ |
2 | /* | 2 | /* |
3 | * Which implementation for busybox | ||
4 | * | ||
5 | * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> | 3 | * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> |
6 | * Copyright (C) 2006 Gabriel Somlo <somlo at cmu.edu> | 4 | * Copyright (C) 2006 Gabriel Somlo <somlo at cmu.edu> |
7 | * | 5 | * |
8 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | 6 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
9 | * | ||
10 | * Based on which from debianutils | ||
11 | */ | 7 | */ |
12 | 8 | ||
13 | //usage:#define which_trivial_usage | 9 | //usage:#define which_trivial_usage |
@@ -24,98 +20,59 @@ | |||
24 | int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 20 | int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
25 | int which_main(int argc UNUSED_PARAM, char **argv) | 21 | int which_main(int argc UNUSED_PARAM, char **argv) |
26 | { | 22 | { |
27 | IF_DESKTOP(int opt;) | 23 | const char *env_path; |
28 | int status = EXIT_SUCCESS; | 24 | int status = 0; |
29 | char *path; | 25 | |
30 | char *p; | 26 | env_path = getenv("PATH"); |
27 | if (!env_path) | ||
28 | env_path = bb_default_root_path; | ||
31 | 29 | ||
32 | opt_complementary = "-1"; /* at least one argument */ | 30 | opt_complementary = "-1"; /* at least one argument */ |
33 | IF_DESKTOP(opt =) getopt32(argv, "a"); | 31 | getopt32(argv, "a"); |
34 | argv += optind; | 32 | argv += optind; |
35 | 33 | ||
36 | /* This matches what is seen on e.g. ubuntu. | ||
37 | * "which" there is a shell script. */ | ||
38 | path = getenv("PATH"); | ||
39 | if (!path) { | ||
40 | path = (char*)bb_PATH_root_path; | ||
41 | putenv(path); | ||
42 | path += 5; /* skip "PATH=" */ | ||
43 | } | ||
44 | |||
45 | do { | 34 | do { |
35 | int missing = 1; | ||
36 | char *p; | ||
37 | |||
46 | #if ENABLE_FEATURE_PREFER_APPLETS | 38 | #if ENABLE_FEATURE_PREFER_APPLETS |
47 | if ( find_applet_by_name(*argv) >= 0 ) { | 39 | if ( find_applet_by_name(*argv) >= 0 ) { |
40 | missing = 0; | ||
48 | puts(*argv); | 41 | puts(*argv); |
49 | IF_DESKTOP(if ( !opt )) | 42 | if (!option_mask32) /* -a not set */ |
50 | continue; | 43 | break; |
51 | } | 44 | } |
52 | #endif | 45 | #endif |
53 | 46 | ||
54 | #if ENABLE_DESKTOP | 47 | /* If file contains a slash don't use PATH */ |
55 | /* Much bloat just to support -a */ | ||
56 | if (strchr(*argv, '/') || (ENABLE_PLATFORM_MINGW32 && strchr(*argv, '\\'))) { | 48 | if (strchr(*argv, '/') || (ENABLE_PLATFORM_MINGW32 && strchr(*argv, '\\'))) { |
57 | if (execable_file(*argv)) { | 49 | if (file_is_executable(*argv)) { |
50 | missing = 0; | ||
58 | puts(*argv); | 51 | puts(*argv); |
59 | continue; | ||
60 | } | 52 | } |
61 | #if ENABLE_PLATFORM_MINGW32 | 53 | #if ENABLE_PLATFORM_MINGW32 |
62 | else if ((p=win32_execable_file(*argv)) != NULL) { | 54 | else if ((p=win32_execable_file(*argv)) != NULL) { |
55 | missing = 0; | ||
63 | puts(p); | 56 | puts(p); |
64 | free(p); | 57 | free(p); |
65 | continue; | ||
66 | } | 58 | } |
67 | #endif | 59 | #endif |
68 | status = EXIT_FAILURE; | ||
69 | } else { | 60 | } else { |
70 | char *path2 = xstrdup(path); | 61 | char *path; |
71 | char *tmp = path2; | 62 | char *tmp; |
72 | 63 | ||
73 | p = find_execable(*argv, &tmp); | 64 | path = tmp = xstrdup(env_path); |
74 | if (!p) | 65 | while ((p = find_executable(*argv, &tmp)) != NULL) { |
75 | status = EXIT_FAILURE; | 66 | missing = 0; |
76 | else { | ||
77 | print: | ||
78 | puts(p); | ||
79 | free(p); | ||
80 | if (opt) { | ||
81 | /* -a: show matches in all PATH components */ | ||
82 | if (tmp) { | ||
83 | p = find_execable(*argv, &tmp); | ||
84 | if (p) | ||
85 | goto print; | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | free(path2); | ||
90 | } | ||
91 | #else | ||
92 | /* Just ignoring -a */ | ||
93 | if (strchr(*argv, '/') || (ENABLE_PLATFORM_MINGW32 && strchr(*argv, '\\'))) { | ||
94 | if (execable_file(*argv)) { | ||
95 | puts(*argv); | ||
96 | continue; | ||
97 | } | ||
98 | #if ENABLE_PLATFORM_MINGW32 | ||
99 | else if ((p=win32_execable_file(*argv)) != NULL) { | ||
100 | puts(p); | ||
101 | free(p); | ||
102 | continue; | ||
103 | } | ||
104 | #endif | ||
105 | } else { | ||
106 | char *path2 = xstrdup(path); | ||
107 | char *tmp = path2; | ||
108 | p = find_execable(*argv, &tmp); | ||
109 | free(path2); | ||
110 | if (p) { | ||
111 | puts(p); | 67 | puts(p); |
112 | free(p); | 68 | free(p); |
113 | continue; | 69 | if (!option_mask32) /* -a not set */ |
70 | break; | ||
114 | } | 71 | } |
72 | free(path); | ||
115 | } | 73 | } |
116 | status = EXIT_FAILURE; | 74 | status |= missing; |
117 | #endif | 75 | } while (*++argv); |
118 | } while (*(++argv) != NULL); | ||
119 | 76 | ||
120 | fflush_stdout_and_exit(status); | 77 | return status; |
121 | } | 78 | } |
diff --git a/docs/ifupdown_design.txt b/docs/ifupdown_design.txt index 8ab4e51ad..39e28a9f4 100644 --- a/docs/ifupdown_design.txt +++ b/docs/ifupdown_design.txt | |||
@@ -21,7 +21,7 @@ static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) | |||
21 | #if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP | 21 | #if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP |
22 | int i ; | 22 | int i ; |
23 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { | 23 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { |
24 | if (exists_execable(ext_dhcp_clients[i].name)) | 24 | if (executable_exists(ext_dhcp_clients[i].name)) |
25 | return execute(ext_dhcp_clients[i].stopcmd, ifd, exec); | 25 | return execute(ext_dhcp_clients[i].stopcmd, ifd, exec); |
26 | } | 26 | } |
27 | bb_error_msg("no dhcp clients found, using static interface shutdown"); | 27 | bb_error_msg("no dhcp clients found, using static interface shutdown"); |
diff --git a/docs/new-applet-HOWTO.txt b/docs/new-applet-HOWTO.txt index 6a8054d0e..078e77bce 100644 --- a/docs/new-applet-HOWTO.txt +++ b/docs/new-applet-HOWTO.txt | |||
@@ -6,7 +6,7 @@ This document details the steps you must take to add a new applet to BusyBox. | |||
6 | Credits: | 6 | Credits: |
7 | Matt Kraai - initial writeup | 7 | Matt Kraai - initial writeup |
8 | Mark Whitley - the remix | 8 | Mark Whitley - the remix |
9 | Thomas Lundquist - Trying to keep it updated. | 9 | Thomas Lundquist - trying to keep it updated |
10 | 10 | ||
11 | When doing this you should consider using the latest git HEAD. | 11 | When doing this you should consider using the latest git HEAD. |
12 | This is a good thing if you plan to getting it committed into mainline. | 12 | This is a good thing if you plan to getting it committed into mainline. |
@@ -16,14 +16,14 @@ Initial Write | |||
16 | 16 | ||
17 | First, write your applet. Be sure to include copyright information at the top, | 17 | First, write your applet. Be sure to include copyright information at the top, |
18 | such as who you stole the code from and so forth. Also include the mini-GPL | 18 | such as who you stole the code from and so forth. Also include the mini-GPL |
19 | boilerplate. Be sure to name the main function <applet>_main instead of main. | 19 | boilerplate and Config.in/Kbuild/usage/applet.h snippets (more on that below |
20 | And be sure to put it in <applet>.c. Usage does not have to be taken care of by | 20 | in this document). Be sure to name the main function <applet>_main instead |
21 | your applet. | 21 | of main. And be sure to put it in <applet>.c. Make sure to #include "libbb.h" |
22 | Make sure to #include "libbb.h" as the first include file in your applet. | 22 | as the first include file in your applet. |
23 | 23 | ||
24 | For a new applet mu, here is the code that would go in mu.c: | 24 | For a new applet mu, here is the code that would go in mu.c: |
25 | 25 | ||
26 | (busybox.h already includes most usual header files. You do not need | 26 | (libbb.h already includes most usual header files. You do not need |
27 | #include <stdio.h> etc...) | 27 | #include <stdio.h> etc...) |
28 | 28 | ||
29 | 29 | ||
@@ -41,6 +41,22 @@ For a new applet mu, here is the code that would go in mu.c: | |||
41 | #include "libbb.h" | 41 | #include "libbb.h" |
42 | #include "other.h" | 42 | #include "other.h" |
43 | 43 | ||
44 | //config:config MU | ||
45 | //config: bool "MU" | ||
46 | //config: default y | ||
47 | //config: help | ||
48 | //config: Returns an indeterminate value. | ||
49 | |||
50 | //kbuild:lib-$(CONFIG_MU) += mu.o | ||
51 | //applet:IF_MU(APPLET(mu, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
52 | |||
53 | //usage:#define mu_trivial_usage | ||
54 | //usage: "[-abcde] FILE..." | ||
55 | //usage:#define mu_full_usage | ||
56 | //usage: "Returns an indeterminate value\n" | ||
57 | //usage: "\n -a First function" | ||
58 | //usage: "\n -b Second function" | ||
59 | |||
44 | int mu_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 60 | int mu_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
45 | int mu_main(int argc, char **argv) | 61 | int mu_main(int argc, char **argv) |
46 | { | 62 | { |
@@ -90,6 +106,8 @@ Make a new file named <function_name>.c | |||
90 | #include "libbb.h" | 106 | #include "libbb.h" |
91 | #include "other.h" | 107 | #include "other.h" |
92 | 108 | ||
109 | //kbuild:lib-y += function.o | ||
110 | |||
93 | int function(char *a) | 111 | int function(char *a) |
94 | { | 112 | { |
95 | return *a; | 113 | return *a; |
@@ -97,9 +115,7 @@ int function(char *a) | |||
97 | 115 | ||
98 | ----end example code------ | 116 | ----end example code------ |
99 | 117 | ||
100 | Add <function_name>.o in the right alphabetically sorted place | 118 | Remember about the kbuild snippet. |
101 | in libbb/Kbuild.src. You should look at the conditional part of | ||
102 | libbb/Kbuild.src as well. | ||
103 | 119 | ||
104 | You should also try to find a suitable place in include/libbb.h for | 120 | You should also try to find a suitable place in include/libbb.h for |
105 | the function declaration. If not, add it somewhere anyway, with or without | 121 | the function declaration. If not, add it somewhere anyway, with or without |
@@ -109,60 +125,68 @@ You can look at libbb/Config.src and try to find out if the function is | |||
109 | tunable and add it there if it is. | 125 | tunable and add it there if it is. |
110 | 126 | ||
111 | 127 | ||
128 | Kbuild/Config.in/usage/applets.h snippets in .c files | ||
129 | ----------------------------------------------------- | ||
130 | |||
131 | The old way of adding new applets was to put all the information needed by the | ||
132 | configuration and build system into appropriate files (namely: Kbuild.src and | ||
133 | Config.src in new applet's directory) and to add the applet declaration and | ||
134 | usage info text to include/applets.src.h and include/usage.src.h respectively. | ||
135 | |||
136 | Since the scripts/gen_build_files.sh script had been introduced, the preferred | ||
137 | way is to have all these declarations contained within the applet .c files. | ||
138 | |||
139 | Every line intended to be processed by gen_build_files.sh should start as a | ||
140 | comment without any preceding whitespaces and be followed by an appropriate | ||
141 | keyword - kbuild, config, usage or applet - and a colon, just like shown in the | ||
142 | first example above. | ||
143 | |||
144 | |||
112 | Placement / Directory | 145 | Placement / Directory |
113 | --------------------- | 146 | --------------------- |
114 | 147 | ||
115 | Find the appropriate directory for your new applet. | 148 | Find the appropriate directory for your new applet. |
116 | 149 | ||
117 | Make sure you find the appropriate places in the files, the applets are | 150 | Add the kbuild snippet to the .c file: |
118 | sorted alphabetically. | ||
119 | 151 | ||
120 | Add the applet to Kbuild.src in the chosen directory: | 152 | //kbuild:lib-$(CONFIG_MU) += mu.o |
121 | 153 | ||
122 | lib-$(CONFIG_MU) += mu.o | 154 | Add the config snippet to the .c file: |
123 | 155 | ||
124 | Add the applet to Config.src in the chosen directory: | 156 | //config:config MU |
125 | 157 | //config: bool "MU" | |
126 | config MU | 158 | //config: default y |
127 | bool "MU" | 159 | //config: help |
128 | default n | 160 | //config: Returns an indeterminate value. |
129 | help | ||
130 | Returns an indeterminate value. | ||
131 | 161 | ||
132 | 162 | ||
133 | Usage String(s) | 163 | Usage String(s) |
134 | --------------- | 164 | --------------- |
135 | 165 | ||
136 | Next, add usage information for you applet to include/usage.src.h. | 166 | Next, add usage information for your applet to the .c file. |
137 | This should look like the following: | 167 | This should look like the following: |
138 | 168 | ||
139 | #define mu_trivial_usage \ | 169 | //usage:#define mu_trivial_usage |
140 | "-[abcde] FILES" | 170 | //usage: "[-abcde] FILE..." |
141 | #define mu_full_usage \ | 171 | //usage:#define mu_full_usage |
142 | "Returns an indeterminate value.\n\n" \ | 172 | //usage: "Returns an indeterminate value\n" |
143 | "Options:\n" \ | 173 | //usage: "\n -a First function" |
144 | "\t-a\t\tfirst function\n" \ | 174 | //usage: "\n -b Second function" |
145 | "\t-b\t\tsecond function\n" \ | 175 | //usage: ... |
146 | ... | ||
147 | 176 | ||
148 | If your program supports flags, the flags should be mentioned on the first | 177 | If your program supports flags, the flags should be mentioned on the first |
149 | line (-[abcde]) and a detailed description of each flag should go in the | 178 | line ([-abcde]) and a detailed description of each flag should go in the |
150 | mu_full_usage section, one flag per line. (Numerous examples of this | 179 | mu_full_usage section, one flag per line. |
151 | currently exist in usage.src.h.) | ||
152 | 180 | ||
153 | 181 | ||
154 | Header Files | 182 | Header Files |
155 | ------------ | 183 | ------------ |
156 | 184 | ||
157 | Next, add an entry to include/applets.src.h. Be *sure* to keep the list | 185 | Finally add the applet declaration snippet. Be sure to read the top of |
158 | in alphabetical order, or else it will break the binary-search lookup | 186 | applets.src.h before adding your applet - it contains important info |
159 | algorithm in busybox.c and the Gods of BusyBox smite you. Yea, verily: | 187 | on applet macros and conventions. |
160 | |||
161 | Be sure to read the top of applets.src.h before adding your applet. | ||
162 | 188 | ||
163 | /* all programs above here are alphabetically "less than" 'mu' */ | 189 | //applet:IF_MU(APPLET(mu, BB_DIR_USR_BIN, BB_SUID_DROP)) |
164 | IF_MU(APPLET(mu, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
165 | /* all programs below here are alphabetically "greater than" 'mu' */ | ||
166 | 190 | ||
167 | 191 | ||
168 | The Grand Announcement | 192 | The Grand Announcement |
diff --git a/editors/vi.c b/editors/vi.c index ab49b3f7d..a6505e0bf 100644 --- a/editors/vi.c +++ b/editors/vi.c | |||
@@ -17,7 +17,6 @@ | |||
17 | * it would be easier to change the mark when add/delete lines | 17 | * it would be easier to change the mark when add/delete lines |
18 | * More intelligence in refresh() | 18 | * More intelligence in refresh() |
19 | * ":r !cmd" and "!cmd" to filter text through an external command | 19 | * ":r !cmd" and "!cmd" to filter text through an external command |
20 | * A true "undo" facility | ||
21 | * An "ex" line oriented mode- maybe using "cmdedit" | 20 | * An "ex" line oriented mode- maybe using "cmdedit" |
22 | */ | 21 | */ |
23 | 22 | ||
@@ -136,6 +135,36 @@ | |||
136 | //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. | 135 | //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. |
137 | //config: | 136 | //config: |
138 | //config: This is not clean but helps a lot on serial lines and such. | 137 | //config: This is not clean but helps a lot on serial lines and such. |
138 | //config:config FEATURE_VI_UNDO | ||
139 | //config: bool "Support undo command 'u'" | ||
140 | //config: default y | ||
141 | //config: depends on VI | ||
142 | //config: help | ||
143 | //config: Support the 'u' command to undo insertion, deletion, and replacement | ||
144 | //config: of text. | ||
145 | //config:config FEATURE_VI_UNDO_QUEUE | ||
146 | //config: bool "Enable undo operation queuing" | ||
147 | //config: default y | ||
148 | //config: depends on FEATURE_VI_UNDO | ||
149 | //config: help | ||
150 | //config: The vi undo functions can use an intermediate queue to greatly lower | ||
151 | //config: malloc() calls and overhead. When the maximum size of this queue is | ||
152 | //config: reached, the contents of the queue are committed to the undo stack. | ||
153 | //config: This increases the size of the undo code and allows some undo | ||
154 | //config: operations (especially un-typing/backspacing) to be far more useful. | ||
155 | //config:config FEATURE_VI_UNDO_QUEUE_MAX | ||
156 | //config: int "Maximum undo character queue size" | ||
157 | //config: default 256 | ||
158 | //config: range 32 65536 | ||
159 | //config: depends on FEATURE_VI_UNDO_QUEUE | ||
160 | //config: help | ||
161 | //config: This option sets the number of bytes used at runtime for the queue. | ||
162 | //config: Smaller values will create more undo objects and reduce the amount | ||
163 | //config: of typed or backspaced characters that are grouped into one undo | ||
164 | //config: operation; larger values increase the potential size of each undo | ||
165 | //config: and will generally malloc() larger objects and less frequently. | ||
166 | //config: Unless you want more (or less) frequent "undo points" while typing, | ||
167 | //config: you should probably leave this unchanged. | ||
139 | 168 | ||
140 | //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP)) | 169 | //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP)) |
141 | 170 | ||
@@ -277,8 +306,8 @@ struct globals { | |||
277 | smallint editing; // >0 while we are editing a file | 306 | smallint editing; // >0 while we are editing a file |
278 | // [code audit says "can be 0, 1 or 2 only"] | 307 | // [code audit says "can be 0, 1 or 2 only"] |
279 | smallint cmd_mode; // 0=command 1=insert 2=replace | 308 | smallint cmd_mode; // 0=command 1=insert 2=replace |
280 | int file_modified; // buffer contents changed (counter, not flag!) | 309 | int modified_count; // buffer contents changed if !0 |
281 | int last_file_modified; // = -1; | 310 | int last_modified_count; // = -1; |
282 | int save_argc; // how many file names on cmd line | 311 | int save_argc; // how many file names on cmd line |
283 | int cmdcnt; // repetition count | 312 | int cmdcnt; // repetition count |
284 | unsigned rows, columns; // the terminal screen is this size | 313 | unsigned rows, columns; // the terminal screen is this size |
@@ -347,6 +376,42 @@ struct globals { | |||
347 | char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ | 376 | char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ |
348 | 377 | ||
349 | char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; | 378 | char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; |
379 | #if ENABLE_FEATURE_VI_UNDO | ||
380 | // undo_push() operations | ||
381 | #define UNDO_INS 0 | ||
382 | #define UNDO_DEL 1 | ||
383 | #define UNDO_INS_CHAIN 2 | ||
384 | #define UNDO_DEL_CHAIN 3 | ||
385 | // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG | ||
386 | #define UNDO_QUEUED_FLAG 4 | ||
387 | #define UNDO_INS_QUEUED 4 | ||
388 | #define UNDO_DEL_QUEUED 5 | ||
389 | #define UNDO_USE_SPOS 32 | ||
390 | #define UNDO_EMPTY 64 | ||
391 | // Pass-through flags for functions that can be undone | ||
392 | #define NO_UNDO 0 | ||
393 | #define ALLOW_UNDO 1 | ||
394 | #define ALLOW_UNDO_CHAIN 2 | ||
395 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
396 | #define ALLOW_UNDO_QUEUED 3 | ||
397 | char undo_queue_state; | ||
398 | int undo_q; | ||
399 | char *undo_queue_spos; // Start position of queued operation | ||
400 | char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX]; | ||
401 | # else | ||
402 | // If undo queuing disabled, don't invoke the missing queue logic | ||
403 | #define ALLOW_UNDO_QUEUED 1 | ||
404 | # endif | ||
405 | |||
406 | struct undo_object { | ||
407 | struct undo_object *prev; // Linking back avoids list traversal (LIFO) | ||
408 | int start; // Offset where the data should be restored/deleted | ||
409 | int length; // total data size | ||
410 | uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped | ||
411 | char undo_text[1]; // text that was deleted (if deletion) | ||
412 | } *undo_stack_tail; | ||
413 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
414 | |||
350 | }; | 415 | }; |
351 | #define G (*ptr_to_globals) | 416 | #define G (*ptr_to_globals) |
352 | #define text (G.text ) | 417 | #define text (G.text ) |
@@ -358,8 +423,8 @@ struct globals { | |||
358 | #define vi_setops (G.vi_setops ) | 423 | #define vi_setops (G.vi_setops ) |
359 | #define editing (G.editing ) | 424 | #define editing (G.editing ) |
360 | #define cmd_mode (G.cmd_mode ) | 425 | #define cmd_mode (G.cmd_mode ) |
361 | #define file_modified (G.file_modified ) | 426 | #define modified_count (G.modified_count ) |
362 | #define last_file_modified (G.last_file_modified ) | 427 | #define last_modified_count (G.last_modified_count) |
363 | #define save_argc (G.save_argc ) | 428 | #define save_argc (G.save_argc ) |
364 | #define cmdcnt (G.cmdcnt ) | 429 | #define cmdcnt (G.cmdcnt ) |
365 | #define rows (G.rows ) | 430 | #define rows (G.rows ) |
@@ -408,15 +473,24 @@ struct globals { | |||
408 | #define last_modifying_cmd (G.last_modifying_cmd ) | 473 | #define last_modifying_cmd (G.last_modifying_cmd ) |
409 | #define get_input_line__buf (G.get_input_line__buf) | 474 | #define get_input_line__buf (G.get_input_line__buf) |
410 | 475 | ||
476 | #if ENABLE_FEATURE_VI_UNDO | ||
477 | #define undo_stack_tail (G.undo_stack_tail ) | ||
478 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
479 | #define undo_queue_state (G.undo_queue_state) | ||
480 | #define undo_q (G.undo_q ) | ||
481 | #define undo_queue (G.undo_queue ) | ||
482 | #define undo_queue_spos (G.undo_queue_spos ) | ||
483 | # endif | ||
484 | #endif | ||
485 | |||
411 | #define INIT_G() do { \ | 486 | #define INIT_G() do { \ |
412 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | 487 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
413 | last_file_modified = -1; \ | 488 | last_modified_count = -1; \ |
414 | /* "" but has space for 2 chars: */ \ | 489 | /* "" but has space for 2 chars: */ \ |
415 | IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ | 490 | IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ |
416 | } while (0) | 491 | } while (0) |
417 | 492 | ||
418 | 493 | ||
419 | static int init_text_buffer(char *); // init from file or create new | ||
420 | static void edit_file(char *); // edit one file | 494 | static void edit_file(char *); // edit one file |
421 | static void do_cmd(int); // execute a command | 495 | static void do_cmd(int); // execute a command |
422 | static int next_tabstop(int); | 496 | static int next_tabstop(int); |
@@ -437,10 +511,12 @@ static void dot_next(void); // move dot to next line B-o-l | |||
437 | static void dot_prev(void); // move dot to prev line B-o-l | 511 | static void dot_prev(void); // move dot to prev line B-o-l |
438 | static void dot_scroll(int, int); // move the screen up or down | 512 | static void dot_scroll(int, int); // move the screen up or down |
439 | static void dot_skip_over_ws(void); // move dot pat WS | 513 | static void dot_skip_over_ws(void); // move dot pat WS |
440 | static void dot_delete(void); // delete the char at 'dot' | ||
441 | static char *bound_dot(char *); // make sure text[0] <= P < "end" | 514 | static char *bound_dot(char *); // make sure text[0] <= P < "end" |
442 | static char *new_screen(int, int); // malloc virtual screen memory | 515 | static char *new_screen(int, int); // malloc virtual screen memory |
443 | static char *char_insert(char *, char); // insert the char c at 'p' | 516 | #if !ENABLE_FEATURE_VI_UNDO |
517 | #define char_insert(a,b,c) char_insert(a,b) | ||
518 | #endif | ||
519 | static char *char_insert(char *, char, int); // insert the char c at 'p' | ||
444 | // might reallocate text[]! use p += stupid_insert(p, ...), | 520 | // might reallocate text[]! use p += stupid_insert(p, ...), |
445 | // and be careful to not use pointers into potentially freed text[]! | 521 | // and be careful to not use pointers into potentially freed text[]! |
446 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' | 522 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' |
@@ -448,11 +524,17 @@ static int find_range(char **, char **, char); // return pointers for an object | |||
448 | static int st_test(char *, int, int, char *); // helper for skip_thing() | 524 | static int st_test(char *, int, int, char *); // helper for skip_thing() |
449 | static char *skip_thing(char *, int, int, int); // skip some object | 525 | static char *skip_thing(char *, int, int, int); // skip some object |
450 | static char *find_pair(char *, char); // find matching pair () [] {} | 526 | static char *find_pair(char *, char); // find matching pair () [] {} |
451 | static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole | 527 | #if !ENABLE_FEATURE_VI_UNDO |
528 | #define text_hole_delete(a,b,c) text_hole_delete(a,b) | ||
529 | #endif | ||
530 | static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole | ||
452 | // might reallocate text[]! use p += text_hole_make(p, ...), | 531 | // might reallocate text[]! use p += text_hole_make(p, ...), |
453 | // and be careful to not use pointers into potentially freed text[]! | 532 | // and be careful to not use pointers into potentially freed text[]! |
454 | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole | 533 | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole |
455 | static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete | 534 | #if !ENABLE_FEATURE_VI_UNDO |
535 | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) | ||
536 | #endif | ||
537 | static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete | ||
456 | static void show_help(void); // display some help info | 538 | static void show_help(void); // display some help info |
457 | static void rawmode(void); // set "raw" mode on tty | 539 | static void rawmode(void); // set "raw" mode on tty |
458 | static void cookmode(void); // return to "cooked" mode on tty | 540 | static void cookmode(void); // return to "cooked" mode on tty |
@@ -460,7 +542,6 @@ static void cookmode(void); // return to "cooked" mode on tty | |||
460 | static int mysleep(int); | 542 | static int mysleep(int); |
461 | static int readit(void); // read (maybe cursor) key from stdin | 543 | static int readit(void); // read (maybe cursor) key from stdin |
462 | static int get_one_char(void); // read 1 char from stdin | 544 | static int get_one_char(void); // read 1 char from stdin |
463 | static int file_size(const char *); // what is the byte size of "fn" | ||
464 | #if !ENABLE_FEATURE_VI_READONLY | 545 | #if !ENABLE_FEATURE_VI_READONLY |
465 | #define file_insert(fn, p, update_ro_status) file_insert(fn, p) | 546 | #define file_insert(fn, p, update_ro_status) file_insert(fn, p) |
466 | #endif | 547 | #endif |
@@ -495,8 +576,8 @@ static char *char_search(char *, const char *, int, int); // search for pattern | |||
495 | #if ENABLE_FEATURE_VI_COLON | 576 | #if ENABLE_FEATURE_VI_COLON |
496 | static char *get_one_address(char *, int *); // get colon addr, if present | 577 | static char *get_one_address(char *, int *); // get colon addr, if present |
497 | static char *get_address(char *, int *, int *); // get two colon addrs, if present | 578 | static char *get_address(char *, int *, int *); // get two colon addrs, if present |
498 | static void colon(char *); // execute the "colon" mode cmds | ||
499 | #endif | 579 | #endif |
580 | static void colon(char *); // execute the "colon" mode cmds | ||
500 | #if ENABLE_FEATURE_VI_USE_SIGNALS | 581 | #if ENABLE_FEATURE_VI_USE_SIGNALS |
501 | static void winch_sig(int); // catch window size changes | 582 | static void winch_sig(int); // catch window size changes |
502 | static void suspend_sig(int); // catch ctrl-Z | 583 | static void suspend_sig(int); // catch ctrl-Z |
@@ -514,20 +595,36 @@ static void showmatching(char *); // show the matching pair () [] {} | |||
514 | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME | 595 | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME |
515 | // might reallocate text[]! use p += string_insert(p, ...), | 596 | // might reallocate text[]! use p += string_insert(p, ...), |
516 | // and be careful to not use pointers into potentially freed text[]! | 597 | // and be careful to not use pointers into potentially freed text[]! |
517 | static uintptr_t string_insert(char *, const char *); // insert the string at 'p' | 598 | # if !ENABLE_FEATURE_VI_UNDO |
599 | #define string_insert(a,b,c) string_insert(a,b) | ||
600 | # endif | ||
601 | static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' | ||
518 | #endif | 602 | #endif |
519 | #if ENABLE_FEATURE_VI_YANKMARK | 603 | #if ENABLE_FEATURE_VI_YANKMARK |
520 | static char *text_yank(char *, char *, int); // save copy of "p" into a register | 604 | static char *text_yank(char *, char *, int); // save copy of "p" into a register |
521 | static char what_reg(void); // what is letter of current YDreg | 605 | static char what_reg(void); // what is letter of current YDreg |
522 | static void check_context(char); // remember context for '' command | 606 | static void check_context(char); // remember context for '' command |
523 | #endif | 607 | #endif |
608 | #if ENABLE_FEATURE_VI_UNDO | ||
609 | static void flush_undo_data(void); | ||
610 | static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack | ||
611 | static void undo_pop(void); // Undo the last operation | ||
612 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
613 | static void undo_queue_commit(void); // Flush any queued objects to the undo stack | ||
614 | # else | ||
615 | # define undo_queue_commit() ((void)0) | ||
616 | # endif | ||
617 | #else | ||
618 | #define flush_undo_data() ((void)0) | ||
619 | #define undo_queue_commit() ((void)0) | ||
620 | #endif | ||
621 | |||
524 | #if ENABLE_FEATURE_VI_CRASHME | 622 | #if ENABLE_FEATURE_VI_CRASHME |
525 | static void crash_dummy(); | 623 | static void crash_dummy(); |
526 | static void crash_test(); | 624 | static void crash_test(); |
527 | static int crashme = 0; | 625 | static int crashme = 0; |
528 | #endif | 626 | #endif |
529 | 627 | ||
530 | |||
531 | static void write1(const char *out) | 628 | static void write1(const char *out) |
532 | { | 629 | { |
533 | fputs(out, stdout); | 630 | fputs(out, stdout); |
@@ -540,6 +637,14 @@ int vi_main(int argc, char **argv) | |||
540 | 637 | ||
541 | INIT_G(); | 638 | INIT_G(); |
542 | 639 | ||
640 | #if ENABLE_FEATURE_VI_UNDO | ||
641 | /* undo_stack_tail = NULL; - already is */ | ||
642 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
643 | undo_queue_state = UNDO_EMPTY; | ||
644 | /* undo_q = 0; - already is */ | ||
645 | #endif | ||
646 | #endif | ||
647 | |||
543 | #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME | 648 | #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME |
544 | my_pid = getpid(); | 649 | my_pid = getpid(); |
545 | #endif | 650 | #endif |
@@ -618,30 +723,29 @@ int vi_main(int argc, char **argv) | |||
618 | static int init_text_buffer(char *fn) | 723 | static int init_text_buffer(char *fn) |
619 | { | 724 | { |
620 | int rc; | 725 | int rc; |
621 | int size = file_size(fn); // file size. -1 means does not exist. | 726 | |
727 | flush_undo_data(); | ||
728 | modified_count = 0; | ||
729 | last_modified_count = -1; | ||
730 | #if ENABLE_FEATURE_VI_YANKMARK | ||
731 | /* init the marks */ | ||
732 | memset(mark, 0, sizeof(mark)); | ||
733 | #endif | ||
622 | 734 | ||
623 | /* allocate/reallocate text buffer */ | 735 | /* allocate/reallocate text buffer */ |
624 | free(text); | 736 | free(text); |
625 | text_size = size + 10240; | 737 | text_size = 10240; |
626 | screenbegin = dot = end = text = xzalloc(text_size); | 738 | screenbegin = dot = end = text = xzalloc(text_size); |
627 | 739 | ||
628 | if (fn != current_filename) { | 740 | if (fn != current_filename) { |
629 | free(current_filename); | 741 | free(current_filename); |
630 | current_filename = xstrdup(fn); | 742 | current_filename = xstrdup(fn); |
631 | } | 743 | } |
632 | if (size < 0) { | 744 | rc = file_insert(fn, text, 1); |
633 | // file dont exist. Start empty buf with dummy line | 745 | if (rc < 0) { |
634 | char_insert(text, '\n'); | 746 | // file doesnt exist. Start empty buf with dummy line |
635 | rc = 0; | 747 | char_insert(text, '\n', NO_UNDO); |
636 | } else { | ||
637 | rc = file_insert(fn, text, 1); | ||
638 | } | 748 | } |
639 | file_modified = 0; | ||
640 | last_file_modified = -1; | ||
641 | #if ENABLE_FEATURE_VI_YANKMARK | ||
642 | /* init the marks. */ | ||
643 | memset(mark, 0, sizeof(mark)); | ||
644 | #endif | ||
645 | return rc; | 749 | return rc; |
646 | } | 750 | } |
647 | 751 | ||
@@ -756,7 +860,7 @@ static void edit_file(char *fn) | |||
756 | crash_dummy(); // generate a random command | 860 | crash_dummy(); // generate a random command |
757 | } else { | 861 | } else { |
758 | crashme = 0; | 862 | crashme = 0; |
759 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string | 863 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string |
760 | dot = text; | 864 | dot = text; |
761 | refresh(FALSE); | 865 | refresh(FALSE); |
762 | } | 866 | } |
@@ -909,13 +1013,71 @@ static void setops(const char *args, const char *opname, int flg_no, | |||
909 | } | 1013 | } |
910 | #endif | 1014 | #endif |
911 | 1015 | ||
1016 | #endif /* FEATURE_VI_COLON */ | ||
1017 | |||
912 | // buf must be no longer than MAX_INPUT_LEN! | 1018 | // buf must be no longer than MAX_INPUT_LEN! |
913 | static void colon(char *buf) | 1019 | static void colon(char *buf) |
914 | { | 1020 | { |
1021 | #if !ENABLE_FEATURE_VI_COLON | ||
1022 | /* Simple ":cmd" handler with minimal set of commands */ | ||
1023 | char *p = buf; | ||
1024 | int cnt; | ||
1025 | |||
1026 | if (*p == ':') | ||
1027 | p++; | ||
1028 | cnt = strlen(p); | ||
1029 | if (cnt == 0) | ||
1030 | return; | ||
1031 | if (strncmp(p, "quit", cnt) == 0 | ||
1032 | || strncmp(p, "q!", cnt) == 0 | ||
1033 | ) { | ||
1034 | if (modified_count && p[1] != '!') { | ||
1035 | status_line_bold("No write since last change (:%s! overrides)", p); | ||
1036 | } else { | ||
1037 | editing = 0; | ||
1038 | } | ||
1039 | return; | ||
1040 | } | ||
1041 | if (strncmp(p, "write", cnt) == 0 | ||
1042 | || strncmp(p, "wq", cnt) == 0 | ||
1043 | || strncmp(p, "wn", cnt) == 0 | ||
1044 | || (p[0] == 'x' && !p[1]) | ||
1045 | ) { | ||
1046 | cnt = file_write(current_filename, text, end - 1); | ||
1047 | if (cnt < 0) { | ||
1048 | if (cnt == -1) | ||
1049 | status_line_bold("Write error: %s", strerror(errno)); | ||
1050 | } else { | ||
1051 | modified_count = 0; | ||
1052 | last_modified_count = -1; | ||
1053 | status_line("'%s' %dL, %dC", | ||
1054 | current_filename, | ||
1055 | count_lines(text, end - 1), cnt | ||
1056 | ); | ||
1057 | if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' | ||
1058 | || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' | ||
1059 | ) { | ||
1060 | editing = 0; | ||
1061 | } | ||
1062 | } | ||
1063 | return; | ||
1064 | } | ||
1065 | if (strncmp(p, "file", cnt) == 0) { | ||
1066 | last_status_cksum = 0; // force status update | ||
1067 | return; | ||
1068 | } | ||
1069 | if (sscanf(p, "%d", &cnt) > 0) { | ||
1070 | dot = find_line(cnt); | ||
1071 | dot_skip_over_ws(); | ||
1072 | return; | ||
1073 | } | ||
1074 | not_implemented(p); | ||
1075 | #else | ||
1076 | |||
915 | char c, *orig_buf, *buf1, *q, *r; | 1077 | char c, *orig_buf, *buf1, *q, *r; |
916 | char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; | 1078 | char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; |
917 | int i, l, li, ch, b, e; | 1079 | int i, l, li, b, e; |
918 | int useforce, forced = FALSE; | 1080 | int useforce; |
919 | 1081 | ||
920 | // :3154 // if (-e line 3154) goto it else stay put | 1082 | // :3154 // if (-e line 3154) goto it else stay put |
921 | // :4,33w! foo // write a portion of buffer to file "foo" | 1083 | // :4,33w! foo // write a portion of buffer to file "foo" |
@@ -937,7 +1099,7 @@ static void colon(char *buf) | |||
937 | if (*buf == ':') | 1099 | if (*buf == ':') |
938 | buf++; // move past the ':' | 1100 | buf++; // move past the ':' |
939 | 1101 | ||
940 | li = ch = i = 0; | 1102 | li = i = 0; |
941 | b = e = -1; | 1103 | b = e = -1; |
942 | q = text; // assume 1,$ for the range | 1104 | q = text; // assume 1,$ for the range |
943 | r = end - 1; | 1105 | r = end - 1; |
@@ -1015,11 +1177,13 @@ static void colon(char *buf) | |||
1015 | q = begin_line(dot); // assume .,. for the range | 1177 | q = begin_line(dot); // assume .,. for the range |
1016 | r = end_line(dot); | 1178 | r = end_line(dot); |
1017 | } | 1179 | } |
1018 | dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines | 1180 | dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines |
1019 | dot_skip_over_ws(); | 1181 | dot_skip_over_ws(); |
1020 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file | 1182 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file |
1183 | int size; | ||
1184 | |||
1021 | // don't edit, if the current file has been modified | 1185 | // don't edit, if the current file has been modified |
1022 | if (file_modified && !useforce) { | 1186 | if (modified_count && !useforce) { |
1023 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1187 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1024 | goto ret; | 1188 | goto ret; |
1025 | } | 1189 | } |
@@ -1035,8 +1199,7 @@ static void colon(char *buf) | |||
1035 | goto ret; | 1199 | goto ret; |
1036 | } | 1200 | } |
1037 | 1201 | ||
1038 | if (init_text_buffer(fn) < 0) | 1202 | size = init_text_buffer(fn); |
1039 | goto ret; | ||
1040 | 1203 | ||
1041 | #if ENABLE_FEATURE_VI_YANKMARK | 1204 | #if ENABLE_FEATURE_VI_YANKMARK |
1042 | if (Ureg >= 0 && Ureg < 28) { | 1205 | if (Ureg >= 0 && Ureg < 28) { |
@@ -1052,12 +1215,14 @@ static void colon(char *buf) | |||
1052 | li = count_lines(text, end - 1); | 1215 | li = count_lines(text, end - 1); |
1053 | status_line("'%s'%s" | 1216 | status_line("'%s'%s" |
1054 | IF_FEATURE_VI_READONLY("%s") | 1217 | IF_FEATURE_VI_READONLY("%s") |
1055 | " %dL, %dC", current_filename, | 1218 | " %dL, %dC", |
1056 | (file_size(fn) < 0 ? " [New file]" : ""), | 1219 | current_filename, |
1220 | (size < 0 ? " [New file]" : ""), | ||
1057 | IF_FEATURE_VI_READONLY( | 1221 | IF_FEATURE_VI_READONLY( |
1058 | ((readonly_mode) ? " [Readonly]" : ""), | 1222 | ((readonly_mode) ? " [Readonly]" : ""), |
1059 | ) | 1223 | ) |
1060 | li, ch); | 1224 | li, (int)(end - text) |
1225 | ); | ||
1061 | } else if (strncmp(cmd, "file", i) == 0) { // what File is this | 1226 | } else if (strncmp(cmd, "file", i) == 0) { // what File is this |
1062 | if (b != -1 || e != -1) { | 1227 | if (b != -1 || e != -1) { |
1063 | status_line_bold("No address allowed on this command"); | 1228 | status_line_bold("No address allowed on this command"); |
@@ -1122,7 +1287,7 @@ static void colon(char *buf) | |||
1122 | goto ret; | 1287 | goto ret; |
1123 | } | 1288 | } |
1124 | // don't exit if the file been modified | 1289 | // don't exit if the file been modified |
1125 | if (file_modified) { | 1290 | if (modified_count) { |
1126 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1291 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1127 | goto ret; | 1292 | goto ret; |
1128 | } | 1293 | } |
@@ -1146,6 +1311,8 @@ static void colon(char *buf) | |||
1146 | } | 1311 | } |
1147 | editing = 0; | 1312 | editing = 0; |
1148 | } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] | 1313 | } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] |
1314 | int size; | ||
1315 | |||
1149 | fn = args; | 1316 | fn = args; |
1150 | if (!fn[0]) { | 1317 | if (!fn[0]) { |
1151 | status_line_bold("No filename given"); | 1318 | status_line_bold("No filename given"); |
@@ -1159,26 +1326,27 @@ static void colon(char *buf) | |||
1159 | q = next_line(q); | 1326 | q = next_line(q); |
1160 | { // dance around potentially-reallocated text[] | 1327 | { // dance around potentially-reallocated text[] |
1161 | uintptr_t ofs = q - text; | 1328 | uintptr_t ofs = q - text; |
1162 | ch = file_insert(fn, q, 0); | 1329 | size = file_insert(fn, q, /*update_ro:*/ 0); |
1163 | q = text + ofs; | 1330 | q = text + ofs; |
1164 | } | 1331 | } |
1165 | if (ch < 0) | 1332 | if (size < 0) |
1166 | goto ret; // nothing was inserted | 1333 | goto ret; // nothing was inserted |
1167 | // how many lines in text[]? | 1334 | // how many lines in text[]? |
1168 | li = count_lines(q, q + ch - 1); | 1335 | li = count_lines(q, q + size - 1); |
1169 | status_line("'%s'" | 1336 | status_line("'%s'" |
1170 | IF_FEATURE_VI_READONLY("%s") | 1337 | IF_FEATURE_VI_READONLY("%s") |
1171 | " %dL, %dC", fn, | 1338 | " %dL, %dC", |
1339 | fn, | ||
1172 | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) | 1340 | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) |
1173 | li, ch); | 1341 | li, size |
1174 | if (ch > 0) { | 1342 | ); |
1343 | if (size > 0) { | ||
1175 | // if the insert is before "dot" then we need to update | 1344 | // if the insert is before "dot" then we need to update |
1176 | if (q <= dot) | 1345 | if (q <= dot) |
1177 | dot += ch; | 1346 | dot += size; |
1178 | /*file_modified++; - done by file_insert */ | ||
1179 | } | 1347 | } |
1180 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args | 1348 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args |
1181 | if (file_modified && !useforce) { | 1349 | if (modified_count && !useforce) { |
1182 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1350 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1183 | } else { | 1351 | } else { |
1184 | // reset the filenames to edit | 1352 | // reset the filenames to edit |
@@ -1235,6 +1403,9 @@ static void colon(char *buf) | |||
1235 | char *F, *R, *flags; | 1403 | char *F, *R, *flags; |
1236 | size_t len_F, len_R; | 1404 | size_t len_F, len_R; |
1237 | int gflag; // global replace flag | 1405 | int gflag; // global replace flag |
1406 | #if ENABLE_FEATURE_VI_UNDO | ||
1407 | int dont_chain_first_item = ALLOW_UNDO; | ||
1408 | #endif | ||
1238 | 1409 | ||
1239 | // F points to the "find" pattern | 1410 | // F points to the "find" pattern |
1240 | // R points to the "replace" pattern | 1411 | // R points to the "replace" pattern |
@@ -1269,9 +1440,13 @@ static void colon(char *buf) | |||
1269 | if (found) { | 1440 | if (found) { |
1270 | uintptr_t bias; | 1441 | uintptr_t bias; |
1271 | // we found the "find" pattern - delete it | 1442 | // we found the "find" pattern - delete it |
1272 | text_hole_delete(found, found + len_F - 1); | 1443 | // For undo support, the first item should not be chained |
1273 | // inset the "replace" patern | 1444 | text_hole_delete(found, found + len_F - 1, dont_chain_first_item); |
1274 | bias = string_insert(found, R); // insert the string | 1445 | #if ENABLE_FEATURE_VI_UNDO |
1446 | dont_chain_first_item = ALLOW_UNDO_CHAIN; | ||
1447 | #endif | ||
1448 | // insert the "replace" patern | ||
1449 | bias = string_insert(found, R, ALLOW_UNDO_CHAIN); | ||
1275 | found += bias; | 1450 | found += bias; |
1276 | ls += bias; | 1451 | ls += bias; |
1277 | /*q += bias; - recalculated anyway */ | 1452 | /*q += bias; - recalculated anyway */ |
@@ -1293,6 +1468,9 @@ static void colon(char *buf) | |||
1293 | || strncmp(cmd, "wn", i) == 0 | 1468 | || strncmp(cmd, "wn", i) == 0 |
1294 | || (cmd[0] == 'x' && !cmd[1]) | 1469 | || (cmd[0] == 'x' && !cmd[1]) |
1295 | ) { | 1470 | ) { |
1471 | int size; | ||
1472 | //int forced = FALSE; | ||
1473 | |||
1296 | // is there a file name to write to? | 1474 | // is there a file name to write to? |
1297 | if (args[0]) { | 1475 | if (args[0]) { |
1298 | fn = args; | 1476 | fn = args; |
@@ -1305,34 +1483,33 @@ static void colon(char *buf) | |||
1305 | #endif | 1483 | #endif |
1306 | // how many lines in text[]? | 1484 | // how many lines in text[]? |
1307 | li = count_lines(q, r); | 1485 | li = count_lines(q, r); |
1308 | ch = r - q + 1; | 1486 | size = r - q + 1; |
1309 | // see if file exists- if not, its just a new file request | 1487 | //if (useforce) { |
1310 | if (useforce) { | ||
1311 | // if "fn" is not write-able, chmod u+w | 1488 | // if "fn" is not write-able, chmod u+w |
1312 | // sprintf(syscmd, "chmod u+w %s", fn); | 1489 | // sprintf(syscmd, "chmod u+w %s", fn); |
1313 | // system(syscmd); | 1490 | // system(syscmd); |
1314 | forced = TRUE; | 1491 | // forced = TRUE; |
1315 | } | 1492 | //} |
1316 | l = file_write(fn, q, r); | 1493 | l = file_write(fn, q, r); |
1317 | if (useforce && forced) { | 1494 | //if (useforce && forced) { |
1318 | // chmod u-w | 1495 | // chmod u-w |
1319 | // sprintf(syscmd, "chmod u-w %s", fn); | 1496 | // sprintf(syscmd, "chmod u-w %s", fn); |
1320 | // system(syscmd); | 1497 | // system(syscmd); |
1321 | forced = FALSE; | 1498 | // forced = FALSE; |
1322 | } | 1499 | //} |
1323 | if (l < 0) { | 1500 | if (l < 0) { |
1324 | if (l == -1) | 1501 | if (l == -1) |
1325 | status_line_bold_errno(fn); | 1502 | status_line_bold_errno(fn); |
1326 | } else { | 1503 | } else { |
1327 | status_line("'%s' %dL, %dC", fn, li, l); | 1504 | status_line("'%s' %dL, %dC", fn, li, l); |
1328 | if (q == text && r == end - 1 && l == ch) { | 1505 | if (q == text && r == end - 1 && l == size) { |
1329 | file_modified = 0; | 1506 | modified_count = 0; |
1330 | last_file_modified = -1; | 1507 | last_modified_count = -1; |
1331 | } | 1508 | } |
1332 | if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' | 1509 | if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' |
1333 | || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N' | 1510 | || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N' |
1334 | ) | 1511 | ) |
1335 | && l == ch | 1512 | && l == size |
1336 | ) { | 1513 | ) { |
1337 | editing = 0; | 1514 | editing = 0; |
1338 | } | 1515 | } |
@@ -1359,9 +1536,8 @@ static void colon(char *buf) | |||
1359 | colon_s_fail: | 1536 | colon_s_fail: |
1360 | status_line(":s expression missing delimiters"); | 1537 | status_line(":s expression missing delimiters"); |
1361 | #endif | 1538 | #endif |
1362 | } | ||
1363 | |||
1364 | #endif /* FEATURE_VI_COLON */ | 1539 | #endif /* FEATURE_VI_COLON */ |
1540 | } | ||
1365 | 1541 | ||
1366 | static void Hit_Return(void) | 1542 | static void Hit_Return(void) |
1367 | { | 1543 | { |
@@ -1572,23 +1748,27 @@ static char *find_line(int li) // find begining of line #li | |||
1572 | //----- Dot Movement Routines ---------------------------------- | 1748 | //----- Dot Movement Routines ---------------------------------- |
1573 | static void dot_left(void) | 1749 | static void dot_left(void) |
1574 | { | 1750 | { |
1751 | undo_queue_commit(); | ||
1575 | if (dot > text && dot[-1] != '\n') | 1752 | if (dot > text && dot[-1] != '\n') |
1576 | dot--; | 1753 | dot--; |
1577 | } | 1754 | } |
1578 | 1755 | ||
1579 | static void dot_right(void) | 1756 | static void dot_right(void) |
1580 | { | 1757 | { |
1758 | undo_queue_commit(); | ||
1581 | if (dot < end - 1 && *dot != '\n') | 1759 | if (dot < end - 1 && *dot != '\n') |
1582 | dot++; | 1760 | dot++; |
1583 | } | 1761 | } |
1584 | 1762 | ||
1585 | static void dot_begin(void) | 1763 | static void dot_begin(void) |
1586 | { | 1764 | { |
1765 | undo_queue_commit(); | ||
1587 | dot = begin_line(dot); // return pointer to first char cur line | 1766 | dot = begin_line(dot); // return pointer to first char cur line |
1588 | } | 1767 | } |
1589 | 1768 | ||
1590 | static void dot_end(void) | 1769 | static void dot_end(void) |
1591 | { | 1770 | { |
1771 | undo_queue_commit(); | ||
1592 | dot = end_line(dot); // return pointer to last char cur line | 1772 | dot = end_line(dot); // return pointer to last char cur line |
1593 | } | 1773 | } |
1594 | 1774 | ||
@@ -1614,11 +1794,13 @@ static char *move_to_col(char *p, int l) | |||
1614 | 1794 | ||
1615 | static void dot_next(void) | 1795 | static void dot_next(void) |
1616 | { | 1796 | { |
1797 | undo_queue_commit(); | ||
1617 | dot = next_line(dot); | 1798 | dot = next_line(dot); |
1618 | } | 1799 | } |
1619 | 1800 | ||
1620 | static void dot_prev(void) | 1801 | static void dot_prev(void) |
1621 | { | 1802 | { |
1803 | undo_queue_commit(); | ||
1622 | dot = prev_line(dot); | 1804 | dot = prev_line(dot); |
1623 | } | 1805 | } |
1624 | 1806 | ||
@@ -1626,6 +1808,7 @@ static void dot_scroll(int cnt, int dir) | |||
1626 | { | 1808 | { |
1627 | char *q; | 1809 | char *q; |
1628 | 1810 | ||
1811 | undo_queue_commit(); | ||
1629 | for (; cnt > 0; cnt--) { | 1812 | for (; cnt > 0; cnt--) { |
1630 | if (dir < 0) { | 1813 | if (dir < 0) { |
1631 | // scroll Backwards | 1814 | // scroll Backwards |
@@ -1653,11 +1836,6 @@ static void dot_skip_over_ws(void) | |||
1653 | dot++; | 1836 | dot++; |
1654 | } | 1837 | } |
1655 | 1838 | ||
1656 | static void dot_delete(void) // delete the char at 'dot' | ||
1657 | { | ||
1658 | text_hole_delete(dot, dot); | ||
1659 | } | ||
1660 | |||
1661 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" | 1839 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" |
1662 | { | 1840 | { |
1663 | if (p >= end && end > text) { | 1841 | if (p >= end && end > text) { |
@@ -1804,17 +1982,34 @@ static char *char_search(char *p, const char *pat, int dir, int range) | |||
1804 | 1982 | ||
1805 | #endif /* FEATURE_VI_SEARCH */ | 1983 | #endif /* FEATURE_VI_SEARCH */ |
1806 | 1984 | ||
1807 | static char *char_insert(char *p, char c) // insert the char c at 'p' | 1985 | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' |
1808 | { | 1986 | { |
1809 | if (c == 22) { // Is this an ctrl-V? | 1987 | if (c == 22) { // Is this an ctrl-V? |
1810 | p += stupid_insert(p, '^'); // use ^ to indicate literal next | 1988 | p += stupid_insert(p, '^'); // use ^ to indicate literal next |
1811 | refresh(FALSE); // show the ^ | 1989 | refresh(FALSE); // show the ^ |
1812 | c = get_one_char(); | 1990 | c = get_one_char(); |
1813 | *p = c; | 1991 | *p = c; |
1992 | #if ENABLE_FEATURE_VI_UNDO | ||
1993 | switch (undo) { | ||
1994 | case ALLOW_UNDO: | ||
1995 | undo_push(p, 1, UNDO_INS); | ||
1996 | break; | ||
1997 | case ALLOW_UNDO_CHAIN: | ||
1998 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
1999 | break; | ||
2000 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2001 | case ALLOW_UNDO_QUEUED: | ||
2002 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
2003 | break; | ||
2004 | # endif | ||
2005 | } | ||
2006 | #else | ||
2007 | modified_count++; | ||
2008 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1814 | p++; | 2009 | p++; |
1815 | file_modified++; | ||
1816 | } else if (c == 27) { // Is this an ESC? | 2010 | } else if (c == 27) { // Is this an ESC? |
1817 | cmd_mode = 0; | 2011 | cmd_mode = 0; |
2012 | undo_queue_commit(); | ||
1818 | cmdcnt = 0; | 2013 | cmdcnt = 0; |
1819 | end_cmd_q(); // stop adding to q | 2014 | end_cmd_q(); // stop adding to q |
1820 | last_status_cksum = 0; // force status update | 2015 | last_status_cksum = 0; // force status update |
@@ -1825,7 +2020,7 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1825 | // 123456789 | 2020 | // 123456789 |
1826 | if ((p[-1] != '\n') && (dot>text)) { | 2021 | if ((p[-1] != '\n') && (dot>text)) { |
1827 | p--; | 2022 | p--; |
1828 | p = text_hole_delete(p, p); // shrink buffer 1 char | 2023 | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char |
1829 | } | 2024 | } |
1830 | } else { | 2025 | } else { |
1831 | #if ENABLE_FEATURE_VI_SETOPTS | 2026 | #if ENABLE_FEATURE_VI_SETOPTS |
@@ -1838,6 +2033,27 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1838 | #if ENABLE_FEATURE_VI_SETOPTS | 2033 | #if ENABLE_FEATURE_VI_SETOPTS |
1839 | sp = p; // remember addr of insert | 2034 | sp = p; // remember addr of insert |
1840 | #endif | 2035 | #endif |
2036 | #if ENABLE_FEATURE_VI_UNDO | ||
2037 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2038 | if (c == '\n') | ||
2039 | undo_queue_commit(); | ||
2040 | # endif | ||
2041 | switch (undo) { | ||
2042 | case ALLOW_UNDO: | ||
2043 | undo_push(p, 1, UNDO_INS); | ||
2044 | break; | ||
2045 | case ALLOW_UNDO_CHAIN: | ||
2046 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
2047 | break; | ||
2048 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2049 | case ALLOW_UNDO_QUEUED: | ||
2050 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
2051 | break; | ||
2052 | # endif | ||
2053 | } | ||
2054 | #else | ||
2055 | modified_count++; | ||
2056 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1841 | p += 1 + stupid_insert(p, c); // insert the char | 2057 | p += 1 + stupid_insert(p, c); // insert the char |
1842 | #if ENABLE_FEATURE_VI_SETOPTS | 2058 | #if ENABLE_FEATURE_VI_SETOPTS |
1843 | if (showmatch && strchr(")]}", *sp) != NULL) { | 2059 | if (showmatch && strchr(")]}", *sp) != NULL) { |
@@ -1853,6 +2069,9 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1853 | bias = text_hole_make(p, len); | 2069 | bias = text_hole_make(p, len); |
1854 | p += bias; | 2070 | p += bias; |
1855 | q += bias; | 2071 | q += bias; |
2072 | #if ENABLE_FEATURE_VI_UNDO | ||
2073 | undo_push(p, len, UNDO_INS); | ||
2074 | #endif | ||
1856 | memcpy(p, q, len); | 2075 | memcpy(p, q, len); |
1857 | p += len; | 2076 | p += len; |
1858 | } | 2077 | } |
@@ -1870,7 +2089,6 @@ static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at | |||
1870 | bias = text_hole_make(p, 1); | 2089 | bias = text_hole_make(p, 1); |
1871 | p += bias; | 2090 | p += bias; |
1872 | *p = c; | 2091 | *p = c; |
1873 | //file_modified++; - done by text_hole_make() | ||
1874 | return bias; | 2092 | return bias; |
1875 | } | 2093 | } |
1876 | 2094 | ||
@@ -2051,6 +2269,199 @@ static void showmatching(char *p) | |||
2051 | } | 2269 | } |
2052 | #endif /* FEATURE_VI_SETOPTS */ | 2270 | #endif /* FEATURE_VI_SETOPTS */ |
2053 | 2271 | ||
2272 | #if ENABLE_FEATURE_VI_UNDO | ||
2273 | static void flush_undo_data(void) | ||
2274 | { | ||
2275 | struct undo_object *undo_entry; | ||
2276 | |||
2277 | while (undo_stack_tail) { | ||
2278 | undo_entry = undo_stack_tail; | ||
2279 | undo_stack_tail = undo_entry->prev; | ||
2280 | free(undo_entry); | ||
2281 | } | ||
2282 | } | ||
2283 | |||
2284 | // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com) | ||
2285 | static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack | ||
2286 | { | ||
2287 | struct undo_object *undo_entry; | ||
2288 | |||
2289 | // "u_type" values | ||
2290 | // UNDO_INS: insertion, undo will remove from buffer | ||
2291 | // UNDO_DEL: deleted text, undo will restore to buffer | ||
2292 | // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete | ||
2293 | // The CHAIN operations are for handling multiple operations that the user | ||
2294 | // performs with a single action, i.e. REPLACE mode or find-and-replace commands | ||
2295 | // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue | ||
2296 | // for the INS/DEL operation. The raw values should be equal to the values of | ||
2297 | // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG | ||
2298 | |||
2299 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2300 | // This undo queuing functionality groups multiple character typing or backspaces | ||
2301 | // into a single large undo object. This greatly reduces calls to malloc() for | ||
2302 | // single-character operations while typing and has the side benefit of letting | ||
2303 | // an undo operation remove chunks of text rather than a single character. | ||
2304 | switch (u_type) { | ||
2305 | case UNDO_EMPTY: // Just in case this ever happens... | ||
2306 | return; | ||
2307 | case UNDO_DEL_QUEUED: | ||
2308 | if (length != 1) | ||
2309 | return; // Only queue single characters | ||
2310 | switch (undo_queue_state) { | ||
2311 | case UNDO_EMPTY: | ||
2312 | undo_queue_state = UNDO_DEL; | ||
2313 | case UNDO_DEL: | ||
2314 | undo_queue_spos = src; | ||
2315 | undo_q++; | ||
2316 | undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src; | ||
2317 | // If queue is full, dump it into an object | ||
2318 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2319 | undo_queue_commit(); | ||
2320 | return; | ||
2321 | case UNDO_INS: | ||
2322 | // Switch from storing inserted text to deleted text | ||
2323 | undo_queue_commit(); | ||
2324 | undo_push(src, length, UNDO_DEL_QUEUED); | ||
2325 | return; | ||
2326 | } | ||
2327 | break; | ||
2328 | case UNDO_INS_QUEUED: | ||
2329 | if (length != 1) | ||
2330 | return; | ||
2331 | switch (undo_queue_state) { | ||
2332 | case UNDO_EMPTY: | ||
2333 | undo_queue_state = UNDO_INS; | ||
2334 | undo_queue_spos = src; | ||
2335 | case UNDO_INS: | ||
2336 | undo_q++; // Don't need to save any data for insertions | ||
2337 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2338 | undo_queue_commit(); | ||
2339 | return; | ||
2340 | case UNDO_DEL: | ||
2341 | // Switch from storing deleted text to inserted text | ||
2342 | undo_queue_commit(); | ||
2343 | undo_push(src, length, UNDO_INS_QUEUED); | ||
2344 | return; | ||
2345 | } | ||
2346 | break; | ||
2347 | } | ||
2348 | #else | ||
2349 | // If undo queuing is disabled, ignore the queuing flag entirely | ||
2350 | u_type = u_type & ~UNDO_QUEUED_FLAG; | ||
2351 | #endif | ||
2352 | |||
2353 | // Allocate a new undo object | ||
2354 | if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) { | ||
2355 | // For UNDO_DEL objects, save deleted text | ||
2356 | if ((src + length) == end) | ||
2357 | length--; | ||
2358 | // If this deletion empties text[], strip the newline. When the buffer becomes | ||
2359 | // zero-length, a newline is added back, which requires this to compensate. | ||
2360 | undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length); | ||
2361 | memcpy(undo_entry->undo_text, src, length); | ||
2362 | } else { | ||
2363 | undo_entry = xzalloc(sizeof(*undo_entry)); | ||
2364 | } | ||
2365 | undo_entry->length = length; | ||
2366 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2367 | if ((u_type & UNDO_USE_SPOS) != 0) { | ||
2368 | undo_entry->start = undo_queue_spos - text; // use start position from queue | ||
2369 | } else { | ||
2370 | undo_entry->start = src - text; // use offset from start of text buffer | ||
2371 | } | ||
2372 | u_type = (u_type & ~UNDO_USE_SPOS); | ||
2373 | #else | ||
2374 | undo_entry->start = src - text; | ||
2375 | #endif | ||
2376 | undo_entry->u_type = u_type; | ||
2377 | |||
2378 | // Push it on undo stack | ||
2379 | undo_entry->prev = undo_stack_tail; | ||
2380 | undo_stack_tail = undo_entry; | ||
2381 | modified_count++; | ||
2382 | } | ||
2383 | |||
2384 | static void undo_pop(void) // Undo the last operation | ||
2385 | { | ||
2386 | int repeat; | ||
2387 | char *u_start, *u_end; | ||
2388 | struct undo_object *undo_entry; | ||
2389 | |||
2390 | // Commit pending undo queue before popping (should be unnecessary) | ||
2391 | undo_queue_commit(); | ||
2392 | |||
2393 | undo_entry = undo_stack_tail; | ||
2394 | // Check for an empty undo stack | ||
2395 | if (!undo_entry) { | ||
2396 | status_line("Already at oldest change"); | ||
2397 | return; | ||
2398 | } | ||
2399 | |||
2400 | switch (undo_entry->u_type) { | ||
2401 | case UNDO_DEL: | ||
2402 | case UNDO_DEL_CHAIN: | ||
2403 | // make hole and put in text that was deleted; deallocate text | ||
2404 | u_start = text + undo_entry->start; | ||
2405 | text_hole_make(u_start, undo_entry->length); | ||
2406 | memcpy(u_start, undo_entry->undo_text, undo_entry->length); | ||
2407 | status_line("Undo [%d] %s %d chars at position %d", | ||
2408 | modified_count, "restored", | ||
2409 | undo_entry->length, undo_entry->start | ||
2410 | ); | ||
2411 | break; | ||
2412 | case UNDO_INS: | ||
2413 | case UNDO_INS_CHAIN: | ||
2414 | // delete what was inserted | ||
2415 | u_start = undo_entry->start + text; | ||
2416 | u_end = u_start - 1 + undo_entry->length; | ||
2417 | text_hole_delete(u_start, u_end, NO_UNDO); | ||
2418 | status_line("Undo [%d] %s %d chars at position %d", | ||
2419 | modified_count, "deleted", | ||
2420 | undo_entry->length, undo_entry->start | ||
2421 | ); | ||
2422 | break; | ||
2423 | } | ||
2424 | repeat = 0; | ||
2425 | switch (undo_entry->u_type) { | ||
2426 | // If this is the end of a chain, lower modification count and refresh display | ||
2427 | case UNDO_DEL: | ||
2428 | case UNDO_INS: | ||
2429 | dot = (text + undo_entry->start); | ||
2430 | refresh(FALSE); | ||
2431 | break; | ||
2432 | case UNDO_DEL_CHAIN: | ||
2433 | case UNDO_INS_CHAIN: | ||
2434 | repeat = 1; | ||
2435 | break; | ||
2436 | } | ||
2437 | // Deallocate the undo object we just processed | ||
2438 | undo_stack_tail = undo_entry->prev; | ||
2439 | free(undo_entry); | ||
2440 | modified_count--; | ||
2441 | // For chained operations, continue popping all the way down the chain. | ||
2442 | if (repeat) { | ||
2443 | undo_pop(); // Follow the undo chain if one exists | ||
2444 | } | ||
2445 | } | ||
2446 | |||
2447 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2448 | static void undo_queue_commit(void) // Flush any queued objects to the undo stack | ||
2449 | { | ||
2450 | // Pushes the queue object onto the undo stack | ||
2451 | if (undo_q > 0) { | ||
2452 | // Deleted character undo events grow from the end | ||
2453 | undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q, | ||
2454 | undo_q, | ||
2455 | (undo_queue_state | UNDO_USE_SPOS) | ||
2456 | ); | ||
2457 | undo_queue_state = UNDO_EMPTY; | ||
2458 | undo_q = 0; | ||
2459 | } | ||
2460 | } | ||
2461 | #endif | ||
2462 | |||
2463 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
2464 | |||
2054 | // open a hole in text[] | 2465 | // open a hole in text[] |
2055 | // might reallocate text[]! use p += text_hole_make(p, ...), | 2466 | // might reallocate text[]! use p += text_hole_make(p, ...), |
2056 | // and be careful to not use pointers into potentially freed text[]! | 2467 | // and be careful to not use pointers into potentially freed text[]! |
@@ -2082,12 +2493,12 @@ static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte | |||
2082 | } | 2493 | } |
2083 | memmove(p + size, p, end - size - p); | 2494 | memmove(p + size, p, end - size - p); |
2084 | memset(p, ' ', size); // clear new hole | 2495 | memset(p, ' ', size); // clear new hole |
2085 | file_modified++; | ||
2086 | return bias; | 2496 | return bias; |
2087 | } | 2497 | } |
2088 | 2498 | ||
2089 | // close a hole in text[] | 2499 | // close a hole in text[] |
2090 | static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive | 2500 | // "undo" value indicates if this operation should be undo-able |
2501 | static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive | ||
2091 | { | 2502 | { |
2092 | char *src, *dest; | 2503 | char *src, *dest; |
2093 | int cnt, hole_size; | 2504 | int cnt, hole_size; |
@@ -2102,10 +2513,29 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2102 | } | 2513 | } |
2103 | hole_size = q - p + 1; | 2514 | hole_size = q - p + 1; |
2104 | cnt = end - src; | 2515 | cnt = end - src; |
2516 | #if ENABLE_FEATURE_VI_UNDO | ||
2517 | switch (undo) { | ||
2518 | case NO_UNDO: | ||
2519 | break; | ||
2520 | case ALLOW_UNDO: | ||
2521 | undo_push(p, hole_size, UNDO_DEL); | ||
2522 | break; | ||
2523 | case ALLOW_UNDO_CHAIN: | ||
2524 | undo_push(p, hole_size, UNDO_DEL_CHAIN); | ||
2525 | break; | ||
2526 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2527 | case ALLOW_UNDO_QUEUED: | ||
2528 | undo_push(p, hole_size, UNDO_DEL_QUEUED); | ||
2529 | break; | ||
2530 | # endif | ||
2531 | } | ||
2532 | modified_count--; | ||
2533 | #endif | ||
2105 | if (src < text || src > end) | 2534 | if (src < text || src > end) |
2106 | goto thd0; | 2535 | goto thd0; |
2107 | if (dest < text || dest >= end) | 2536 | if (dest < text || dest >= end) |
2108 | goto thd0; | 2537 | goto thd0; |
2538 | modified_count++; | ||
2109 | if (src >= end) | 2539 | if (src >= end) |
2110 | goto thd_atend; // just delete the end of the buffer | 2540 | goto thd_atend; // just delete the end of the buffer |
2111 | memmove(dest, src, cnt); | 2541 | memmove(dest, src, cnt); |
@@ -2115,7 +2545,6 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2115 | dest = end - 1; // make sure dest in below end-1 | 2545 | dest = end - 1; // make sure dest in below end-1 |
2116 | if (end <= text) | 2546 | if (end <= text) |
2117 | dest = end = text; // keep pointers valid | 2547 | dest = end = text; // keep pointers valid |
2118 | file_modified++; | ||
2119 | thd0: | 2548 | thd0: |
2120 | return dest; | 2549 | return dest; |
2121 | } | 2550 | } |
@@ -2123,7 +2552,7 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2123 | // copy text into register, then delete text. | 2552 | // copy text into register, then delete text. |
2124 | // if dist <= 0, do not include, or go past, a NewLine | 2553 | // if dist <= 0, do not include, or go past, a NewLine |
2125 | // | 2554 | // |
2126 | static char *yank_delete(char *start, char *stop, int dist, int yf) | 2555 | static char *yank_delete(char *start, char *stop, int dist, int yf, int undo) |
2127 | { | 2556 | { |
2128 | char *p; | 2557 | char *p; |
2129 | 2558 | ||
@@ -2152,7 +2581,7 @@ static char *yank_delete(char *start, char *stop, int dist, int yf) | |||
2152 | text_yank(start, stop, YDreg); | 2581 | text_yank(start, stop, YDreg); |
2153 | #endif | 2582 | #endif |
2154 | if (yf == YANKDEL) { | 2583 | if (yf == YANKDEL) { |
2155 | p = text_hole_delete(start, stop); | 2584 | p = text_hole_delete(start, stop, undo); |
2156 | } // delete lines | 2585 | } // delete lines |
2157 | return p; | 2586 | return p; |
2158 | } | 2587 | } |
@@ -2218,12 +2647,22 @@ static void end_cmd_q(void) | |||
2218 | || ENABLE_FEATURE_VI_CRASHME | 2647 | || ENABLE_FEATURE_VI_CRASHME |
2219 | // might reallocate text[]! use p += string_insert(p, ...), | 2648 | // might reallocate text[]! use p += string_insert(p, ...), |
2220 | // and be careful to not use pointers into potentially freed text[]! | 2649 | // and be careful to not use pointers into potentially freed text[]! |
2221 | static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p' | 2650 | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' |
2222 | { | 2651 | { |
2223 | uintptr_t bias; | 2652 | uintptr_t bias; |
2224 | int i; | 2653 | int i; |
2225 | 2654 | ||
2226 | i = strlen(s); | 2655 | i = strlen(s); |
2656 | #if ENABLE_FEATURE_VI_UNDO | ||
2657 | switch (undo) { | ||
2658 | case ALLOW_UNDO: | ||
2659 | undo_push(p, i, UNDO_INS); | ||
2660 | break; | ||
2661 | case ALLOW_UNDO_CHAIN: | ||
2662 | undo_push(p, i, UNDO_INS_CHAIN); | ||
2663 | break; | ||
2664 | } | ||
2665 | #endif | ||
2227 | bias = text_hole_make(p, i); | 2666 | bias = text_hole_make(p, i); |
2228 | p += bias; | 2667 | p += bias; |
2229 | memcpy(p, s, i); | 2668 | memcpy(p, s, i); |
@@ -2481,17 +2920,6 @@ static char *get_input_line(const char *prompt) | |||
2481 | #undef buf | 2920 | #undef buf |
2482 | } | 2921 | } |
2483 | 2922 | ||
2484 | static int file_size(const char *fn) // what is the byte size of "fn" | ||
2485 | { | ||
2486 | struct stat st_buf; | ||
2487 | int cnt; | ||
2488 | |||
2489 | cnt = -1; | ||
2490 | if (fn && stat(fn, &st_buf) == 0) // see if file exists | ||
2491 | cnt = (int) st_buf.st_size; | ||
2492 | return cnt; | ||
2493 | } | ||
2494 | |||
2495 | // might reallocate text[]! | 2923 | // might reallocate text[]! |
2496 | static int file_insert(const char *fn, char *p, int update_ro_status) | 2924 | static int file_insert(const char *fn, char *p, int update_ro_status) |
2497 | { | 2925 | { |
@@ -2499,38 +2927,37 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2499 | int fd, size; | 2927 | int fd, size; |
2500 | struct stat statbuf; | 2928 | struct stat statbuf; |
2501 | 2929 | ||
2502 | /* Validate file */ | ||
2503 | if (stat(fn, &statbuf) < 0) { | ||
2504 | status_line_bold_errno(fn); | ||
2505 | goto fi0; | ||
2506 | } | ||
2507 | if (!S_ISREG(statbuf.st_mode)) { | ||
2508 | // This is not a regular file | ||
2509 | status_line_bold("'%s' is not a regular file", fn); | ||
2510 | goto fi0; | ||
2511 | } | ||
2512 | if (p < text || p > end) { | 2930 | if (p < text || p > end) { |
2513 | status_line_bold("Trying to insert file outside of memory"); | 2931 | status_line_bold("Trying to insert file outside of memory"); |
2514 | goto fi0; | 2932 | return cnt; |
2515 | } | 2933 | } |
2516 | 2934 | ||
2517 | // read file to buffer | ||
2518 | fd = open(fn, O_RDONLY); | 2935 | fd = open(fn, O_RDONLY); |
2519 | if (fd < 0) { | 2936 | if (fd < 0) { |
2520 | status_line_bold_errno(fn); | 2937 | status_line_bold_errno(fn); |
2521 | goto fi0; | 2938 | return cnt; |
2939 | } | ||
2940 | |||
2941 | /* Validate file */ | ||
2942 | if (fstat(fd, &statbuf) < 0) { | ||
2943 | status_line_bold_errno(fn); | ||
2944 | goto fi; | ||
2945 | } | ||
2946 | if (!S_ISREG(statbuf.st_mode)) { | ||
2947 | status_line_bold("'%s' is not a regular file", fn); | ||
2948 | goto fi; | ||
2522 | } | 2949 | } |
2523 | #if ENABLE_PLATFORM_MINGW32 | 2950 | #if ENABLE_PLATFORM_MINGW32 |
2524 | _setmode(fd, _O_TEXT); | 2951 | _setmode(fd, _O_TEXT); |
2525 | #endif | 2952 | #endif |
2526 | size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); | 2953 | size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); |
2527 | p += text_hole_make(p, size); | 2954 | p += text_hole_make(p, size); |
2528 | cnt = safe_read(fd, p, size); | 2955 | cnt = full_read(fd, p, size); |
2529 | if (cnt < 0) { | 2956 | if (cnt < 0) { |
2530 | status_line_bold_errno(fn); | 2957 | status_line_bold_errno(fn); |
2531 | p = text_hole_delete(p, p + size - 1); // un-do buffer insert | 2958 | p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert |
2532 | } else if (cnt < size) { | 2959 | } else if (cnt < size) { |
2533 | // There was a partial read, shrink unused space text[] | 2960 | // There was a partial read, shrink unused space |
2534 | #if ENABLE_PLATFORM_MINGW32 | 2961 | #if ENABLE_PLATFORM_MINGW32 |
2535 | int i, newline; | 2962 | int i, newline; |
2536 | 2963 | ||
@@ -2541,7 +2968,7 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2541 | } | 2968 | } |
2542 | } | 2969 | } |
2543 | #endif | 2970 | #endif |
2544 | p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert | 2971 | p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); |
2545 | #if ENABLE_PLATFORM_MINGW32 | 2972 | #if ENABLE_PLATFORM_MINGW32 |
2546 | // on WIN32 a partial read might just mean CRs have been removed | 2973 | // on WIN32 a partial read might just mean CRs have been removed |
2547 | if ( cnt+newline != size ) { | 2974 | if ( cnt+newline != size ) { |
@@ -2551,10 +2978,9 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2551 | status_line_bold("can't read '%s'", fn); | 2978 | status_line_bold("can't read '%s'", fn); |
2552 | #endif | 2979 | #endif |
2553 | } | 2980 | } |
2554 | if (cnt >= size) | 2981 | fi: |
2555 | file_modified++; | ||
2556 | close(fd); | 2982 | close(fd); |
2557 | fi0: | 2983 | |
2558 | #if ENABLE_FEATURE_VI_READONLY | 2984 | #if ENABLE_FEATURE_VI_READONLY |
2559 | if (update_ro_status | 2985 | if (update_ro_status |
2560 | && ((access(fn, W_OK) < 0) || | 2986 | && ((access(fn, W_OK) < 0) || |
@@ -2607,7 +3033,7 @@ static int file_write(char *fn, char *first, char *last) | |||
2607 | #endif | 3033 | #endif |
2608 | if (charcnt == cnt) { | 3034 | if (charcnt == cnt) { |
2609 | // good write | 3035 | // good write |
2610 | //file_modified = FALSE; | 3036 | //modified_count = FALSE; |
2611 | } else { | 3037 | } else { |
2612 | charcnt = 0; | 3038 | charcnt = 0; |
2613 | } | 3039 | } |
@@ -2829,7 +3255,7 @@ static int format_edit_status(void) | |||
2829 | 3255 | ||
2830 | int cur, percent, ret, trunc_at; | 3256 | int cur, percent, ret, trunc_at; |
2831 | 3257 | ||
2832 | // file_modified is now a counter rather than a flag. this | 3258 | // modified_count is now a counter rather than a flag. this |
2833 | // helps reduce the amount of line counting we need to do. | 3259 | // helps reduce the amount of line counting we need to do. |
2834 | // (this will cause a mis-reporting of modified status | 3260 | // (this will cause a mis-reporting of modified status |
2835 | // once every MAXINT editing operations.) | 3261 | // once every MAXINT editing operations.) |
@@ -2839,11 +3265,12 @@ static int format_edit_status(void) | |||
2839 | // we're on, then we shouldn't have to do this count_lines() | 3265 | // we're on, then we shouldn't have to do this count_lines() |
2840 | cur = count_lines(text, dot); | 3266 | cur = count_lines(text, dot); |
2841 | 3267 | ||
2842 | // reduce counting -- the total lines can't have | 3268 | // count_lines() is expensive. |
2843 | // changed if we haven't done any edits. | 3269 | // Call it only if something was changed since last time |
2844 | if (file_modified != last_file_modified) { | 3270 | // we were here: |
3271 | if (modified_count != last_modified_count) { | ||
2845 | tot = cur + count_lines(dot, end - 1) - 1; | 3272 | tot = cur + count_lines(dot, end - 1) - 1; |
2846 | last_file_modified = file_modified; | 3273 | last_modified_count = modified_count; |
2847 | } | 3274 | } |
2848 | 3275 | ||
2849 | // current line percent | 3276 | // current line percent |
@@ -2870,7 +3297,7 @@ static int format_edit_status(void) | |||
2870 | #if ENABLE_FEATURE_VI_READONLY | 3297 | #if ENABLE_FEATURE_VI_READONLY |
2871 | (readonly_mode ? " [Readonly]" : ""), | 3298 | (readonly_mode ? " [Readonly]" : ""), |
2872 | #endif | 3299 | #endif |
2873 | (file_modified ? " [Modified]" : ""), | 3300 | (modified_count ? " [Modified]" : ""), |
2874 | cur, tot, percent); | 3301 | cur, tot, percent); |
2875 | 3302 | ||
2876 | if (ret >= 0 && ret < trunc_at) | 3303 | if (ret >= 0 && ret < trunc_at) |
@@ -3095,11 +3522,12 @@ static void do_cmd(int c) | |||
3095 | if (*dot == '\n') { | 3522 | if (*dot == '\n') { |
3096 | // don't Replace past E-o-l | 3523 | // don't Replace past E-o-l |
3097 | cmd_mode = 1; // convert to insert | 3524 | cmd_mode = 1; // convert to insert |
3525 | undo_queue_commit(); | ||
3098 | } else { | 3526 | } else { |
3099 | if (1 <= c || Isprint(c)) { | 3527 | if (1 <= c || Isprint(c)) { |
3100 | if (c != 27) | 3528 | if (c != 27) |
3101 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 3529 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3102 | dot = char_insert(dot, c); // insert new char | 3530 | dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char |
3103 | } | 3531 | } |
3104 | goto dc1; | 3532 | goto dc1; |
3105 | } | 3533 | } |
@@ -3109,7 +3537,7 @@ static void do_cmd(int c) | |||
3109 | if (c == KEYCODE_INSERT) goto dc5; | 3537 | if (c == KEYCODE_INSERT) goto dc5; |
3110 | // insert the char c at "dot" | 3538 | // insert the char c at "dot" |
3111 | if (1 <= c || Isprint(c)) { | 3539 | if (1 <= c || Isprint(c)) { |
3112 | dot = char_insert(dot, c); | 3540 | dot = char_insert(dot, c, ALLOW_UNDO_QUEUED); |
3113 | } | 3541 | } |
3114 | goto dc1; | 3542 | goto dc1; |
3115 | } | 3543 | } |
@@ -3155,7 +3583,6 @@ static void do_cmd(int c) | |||
3155 | //case ']': // ]- | 3583 | //case ']': // ]- |
3156 | //case '_': // _- | 3584 | //case '_': // _- |
3157 | //case '`': // `- | 3585 | //case '`': // `- |
3158 | //case 'u': // u- FIXME- there is no undo | ||
3159 | //case 'v': // v- | 3586 | //case 'v': // v- |
3160 | default: // unrecognized command | 3587 | default: // unrecognized command |
3161 | buf[0] = c; | 3588 | buf[0] = c; |
@@ -3224,6 +3651,7 @@ static void do_cmd(int c) | |||
3224 | if (cmd_mode == 0) | 3651 | if (cmd_mode == 0) |
3225 | indicate_error(c); | 3652 | indicate_error(c); |
3226 | cmd_mode = 0; // stop insrting | 3653 | cmd_mode = 0; // stop insrting |
3654 | undo_queue_commit(); | ||
3227 | end_cmd_q(); | 3655 | end_cmd_q(); |
3228 | last_status_cksum = 0; // force status update | 3656 | last_status_cksum = 0; // force status update |
3229 | break; | 3657 | break; |
@@ -3298,15 +3726,20 @@ static void do_cmd(int c) | |||
3298 | if (c == 'p') | 3726 | if (c == 'p') |
3299 | dot_right(); // move to right, can move to NL | 3727 | dot_right(); // move to right, can move to NL |
3300 | } | 3728 | } |
3301 | string_insert(dot, p); // insert the string | 3729 | string_insert(dot, p, ALLOW_UNDO); // insert the string |
3302 | end_cmd_q(); // stop adding to q | 3730 | end_cmd_q(); // stop adding to q |
3303 | break; | 3731 | break; |
3732 | #if ENABLE_FEATURE_VI_UNDO | ||
3733 | case 'u': // u- undo last operation | ||
3734 | undo_pop(); | ||
3735 | break; | ||
3736 | #endif | ||
3304 | case 'U': // U- Undo; replace current line with original version | 3737 | case 'U': // U- Undo; replace current line with original version |
3305 | if (reg[Ureg] != NULL) { | 3738 | if (reg[Ureg] != NULL) { |
3306 | p = begin_line(dot); | 3739 | p = begin_line(dot); |
3307 | q = end_line(dot); | 3740 | q = end_line(dot); |
3308 | p = text_hole_delete(p, q); // delete cur line | 3741 | p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line |
3309 | p += string_insert(p, reg[Ureg]); // insert orig line | 3742 | p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line |
3310 | dot = p; | 3743 | dot = p; |
3311 | dot_skip_over_ws(); | 3744 | dot_skip_over_ws(); |
3312 | } | 3745 | } |
@@ -3482,57 +3915,14 @@ static void do_cmd(int c) | |||
3482 | break; | 3915 | break; |
3483 | case ':': // :- the colon mode commands | 3916 | case ':': // :- the colon mode commands |
3484 | p = get_input_line(":"); // get input line- use "status line" | 3917 | p = get_input_line(":"); // get input line- use "status line" |
3485 | #if ENABLE_FEATURE_VI_COLON | ||
3486 | colon(p); // execute the command | 3918 | colon(p); // execute the command |
3487 | #else | ||
3488 | if (*p == ':') | ||
3489 | p++; // move past the ':' | ||
3490 | cnt = strlen(p); | ||
3491 | if (cnt <= 0) | ||
3492 | break; | ||
3493 | if (strncmp(p, "quit", cnt) == 0 | ||
3494 | || strncmp(p, "q!", cnt) == 0 // delete lines | ||
3495 | ) { | ||
3496 | if (file_modified && p[1] != '!') { | ||
3497 | status_line_bold("No write since last change (:%s! overrides)", p); | ||
3498 | } else { | ||
3499 | editing = 0; | ||
3500 | } | ||
3501 | } else if (strncmp(p, "write", cnt) == 0 | ||
3502 | || strncmp(p, "wq", cnt) == 0 | ||
3503 | || strncmp(p, "wn", cnt) == 0 | ||
3504 | || (p[0] == 'x' && !p[1]) | ||
3505 | ) { | ||
3506 | cnt = file_write(current_filename, text, end - 1); | ||
3507 | if (cnt < 0) { | ||
3508 | if (cnt == -1) | ||
3509 | status_line_bold("Write error: %s", strerror(errno)); | ||
3510 | } else { | ||
3511 | file_modified = 0; | ||
3512 | last_file_modified = -1; | ||
3513 | status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt); | ||
3514 | if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' | ||
3515 | || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' | ||
3516 | ) { | ||
3517 | editing = 0; | ||
3518 | } | ||
3519 | } | ||
3520 | } else if (strncmp(p, "file", cnt) == 0) { | ||
3521 | last_status_cksum = 0; // force status update | ||
3522 | } else if (sscanf(p, "%d", &j) > 0) { | ||
3523 | dot = find_line(j); // go to line # j | ||
3524 | dot_skip_over_ws(); | ||
3525 | } else { // unrecognized cmd | ||
3526 | not_implemented(p); | ||
3527 | } | ||
3528 | #endif /* !FEATURE_VI_COLON */ | ||
3529 | break; | 3919 | break; |
3530 | case '<': // <- Left shift something | 3920 | case '<': // <- Left shift something |
3531 | case '>': // >- Right shift something | 3921 | case '>': // >- Right shift something |
3532 | cnt = count_lines(text, dot); // remember what line we are on | 3922 | cnt = count_lines(text, dot); // remember what line we are on |
3533 | c1 = get_one_char(); // get the type of thing to delete | 3923 | c1 = get_one_char(); // get the type of thing to delete |
3534 | find_range(&p, &q, c1); | 3924 | find_range(&p, &q, c1); |
3535 | yank_delete(p, q, 1, YANKONLY); // save copy before change | 3925 | yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change |
3536 | p = begin_line(p); | 3926 | p = begin_line(p); |
3537 | q = end_line(q); | 3927 | q = end_line(q); |
3538 | i = count_lines(p, q); // # of lines we are shifting | 3928 | i = count_lines(p, q); // # of lines we are shifting |
@@ -3541,16 +3931,16 @@ static void do_cmd(int c) | |||
3541 | // shift left- remove tab or 8 spaces | 3931 | // shift left- remove tab or 8 spaces |
3542 | if (*p == '\t') { | 3932 | if (*p == '\t') { |
3543 | // shrink buffer 1 char | 3933 | // shrink buffer 1 char |
3544 | text_hole_delete(p, p); | 3934 | text_hole_delete(p, p, NO_UNDO); |
3545 | } else if (*p == ' ') { | 3935 | } else if (*p == ' ') { |
3546 | // we should be calculating columns, not just SPACE | 3936 | // we should be calculating columns, not just SPACE |
3547 | for (j = 0; *p == ' ' && j < tabstop; j++) { | 3937 | for (j = 0; *p == ' ' && j < tabstop; j++) { |
3548 | text_hole_delete(p, p); | 3938 | text_hole_delete(p, p, NO_UNDO); |
3549 | } | 3939 | } |
3550 | } | 3940 | } |
3551 | } else if (c == '>') { | 3941 | } else if (c == '>') { |
3552 | // shift right -- add tab or 8 spaces | 3942 | // shift right -- add tab or 8 spaces |
3553 | char_insert(p, '\t'); | 3943 | char_insert(p, '\t', ALLOW_UNDO); |
3554 | } | 3944 | } |
3555 | } | 3945 | } |
3556 | dot = find_line(cnt); // what line were we on | 3946 | dot = find_line(cnt); // what line were we on |
@@ -3585,7 +3975,7 @@ static void do_cmd(int c) | |||
3585 | save_dot = dot; | 3975 | save_dot = dot; |
3586 | dot = dollar_line(dot); // move to before NL | 3976 | dot = dollar_line(dot); // move to before NL |
3587 | // copy text into a register and delete | 3977 | // copy text into a register and delete |
3588 | dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l | 3978 | dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l |
3589 | if (c == 'C') | 3979 | if (c == 'C') |
3590 | goto dc_i; // start inserting | 3980 | goto dc_i; // start inserting |
3591 | #if ENABLE_FEATURE_VI_DOT_CMD | 3981 | #if ENABLE_FEATURE_VI_DOT_CMD |
@@ -3630,15 +4020,22 @@ static void do_cmd(int c) | |||
3630 | case KEYCODE_INSERT: // Cursor Key Insert | 4020 | case KEYCODE_INSERT: // Cursor Key Insert |
3631 | dc_i: | 4021 | dc_i: |
3632 | cmd_mode = 1; // start inserting | 4022 | cmd_mode = 1; // start inserting |
4023 | undo_queue_commit(); // commit queue when cmd_mode changes | ||
3633 | break; | 4024 | break; |
3634 | case 'J': // J- join current and next lines together | 4025 | case 'J': // J- join current and next lines together |
3635 | do { | 4026 | do { |
3636 | dot_end(); // move to NL | 4027 | dot_end(); // move to NL |
3637 | if (dot < end - 1) { // make sure not last char in text[] | 4028 | if (dot < end - 1) { // make sure not last char in text[] |
4029 | #if ENABLE_FEATURE_VI_UNDO | ||
4030 | undo_push(dot, 1, UNDO_DEL); | ||
3638 | *dot++ = ' '; // replace NL with space | 4031 | *dot++ = ' '; // replace NL with space |
3639 | file_modified++; | 4032 | undo_push((dot - 1), 1, UNDO_INS_CHAIN); |
4033 | #else | ||
4034 | *dot++ = ' '; | ||
4035 | modified_count++; | ||
4036 | #endif | ||
3640 | while (isblank(*dot)) { // delete leading WS | 4037 | while (isblank(*dot)) { // delete leading WS |
3641 | dot_delete(); | 4038 | text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN); |
3642 | } | 4039 | } |
3643 | } | 4040 | } |
3644 | } while (--cmdcnt > 0); | 4041 | } while (--cmdcnt > 0); |
@@ -3667,10 +4064,10 @@ static void do_cmd(int c) | |||
3667 | dot_prev(); | 4064 | dot_prev(); |
3668 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." | 4065 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." |
3669 | dot_end(); | 4066 | dot_end(); |
3670 | dot = char_insert(dot, '\n'); | 4067 | dot = char_insert(dot, '\n', ALLOW_UNDO); |
3671 | } else { | 4068 | } else { |
3672 | dot_begin(); // 0 | 4069 | dot_begin(); // 0 |
3673 | dot = char_insert(dot, '\n'); // i\n ESC | 4070 | dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC |
3674 | dot_prev(); // - | 4071 | dot_prev(); // - |
3675 | } | 4072 | } |
3676 | goto dc_i; | 4073 | goto dc_i; |
@@ -3678,6 +4075,7 @@ static void do_cmd(int c) | |||
3678 | case 'R': // R- continuous Replace char | 4075 | case 'R': // R- continuous Replace char |
3679 | dc5: | 4076 | dc5: |
3680 | cmd_mode = 2; | 4077 | cmd_mode = 2; |
4078 | undo_queue_commit(); | ||
3681 | break; | 4079 | break; |
3682 | case KEYCODE_DELETE: | 4080 | case KEYCODE_DELETE: |
3683 | c = 'x'; | 4081 | c = 'x'; |
@@ -3692,7 +4090,7 @@ static void do_cmd(int c) | |||
3692 | if (dot[dir] != '\n') { | 4090 | if (dot[dir] != '\n') { |
3693 | if (c == 'X') | 4091 | if (c == 'X') |
3694 | dot--; // delete prev char | 4092 | dot--; // delete prev char |
3695 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 4093 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3696 | } | 4094 | } |
3697 | } while (--cmdcnt > 0); | 4095 | } while (--cmdcnt > 0); |
3698 | end_cmd_q(); // stop adding to q | 4096 | end_cmd_q(); // stop adding to q |
@@ -3706,7 +4104,7 @@ static void do_cmd(int c) | |||
3706 | indicate_error(c); | 4104 | indicate_error(c); |
3707 | break; | 4105 | break; |
3708 | } | 4106 | } |
3709 | if (file_modified) { | 4107 | if (modified_count) { |
3710 | if (ENABLE_FEATURE_VI_READONLY && readonly_mode) { | 4108 | if (ENABLE_FEATURE_VI_READONLY && readonly_mode) { |
3711 | status_line_bold("'%s' is read only", current_filename); | 4109 | status_line_bold("'%s' is read only", current_filename); |
3712 | break; | 4110 | break; |
@@ -3763,6 +4161,7 @@ static void do_cmd(int c) | |||
3763 | c1 = get_one_char(); // get the type of thing to delete | 4161 | c1 = get_one_char(); // get the type of thing to delete |
3764 | // determine range, and whether it spans lines | 4162 | // determine range, and whether it spans lines |
3765 | ml = find_range(&p, &q, c1); | 4163 | ml = find_range(&p, &q, c1); |
4164 | place_cursor(0, 0); | ||
3766 | if (c1 == 27) { // ESC- user changed mind and wants out | 4165 | if (c1 == 27) { // ESC- user changed mind and wants out |
3767 | c = c1 = 27; // Escape- do nothing | 4166 | c = c1 = 27; // Escape- do nothing |
3768 | } else if (strchr("wW", c1)) { | 4167 | } else if (strchr("wW", c1)) { |
@@ -3774,13 +4173,13 @@ static void do_cmd(int c) | |||
3774 | q--; | 4173 | q--; |
3775 | } | 4174 | } |
3776 | } | 4175 | } |
3777 | dot = yank_delete(p, q, ml, yf); // delete word | 4176 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3778 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { | 4177 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { |
3779 | // partial line copy text into a register and delete | 4178 | // partial line copy text into a register and delete |
3780 | dot = yank_delete(p, q, ml, yf); // delete word | 4179 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3781 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { | 4180 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { |
3782 | // whole line copy text into a register and delete | 4181 | // whole line copy text into a register and delete |
3783 | dot = yank_delete(p, q, ml, yf); // delete lines | 4182 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines |
3784 | whole = 1; | 4183 | whole = 1; |
3785 | } else { | 4184 | } else { |
3786 | // could not recognize object | 4185 | // could not recognize object |
@@ -3790,7 +4189,7 @@ static void do_cmd(int c) | |||
3790 | } | 4189 | } |
3791 | if (ml && whole) { | 4190 | if (ml && whole) { |
3792 | if (c == 'c') { | 4191 | if (c == 'c') { |
3793 | dot = char_insert(dot, '\n'); | 4192 | dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN); |
3794 | // on the last line of file don't move to prev line | 4193 | // on the last line of file don't move to prev line |
3795 | if (whole && dot != (end-1)) { | 4194 | if (whole && dot != (end-1)) { |
3796 | dot_prev(); | 4195 | dot_prev(); |
@@ -3836,8 +4235,14 @@ static void do_cmd(int c) | |||
3836 | case 'r': // r- replace the current char with user input | 4235 | case 'r': // r- replace the current char with user input |
3837 | c1 = get_one_char(); // get the replacement char | 4236 | c1 = get_one_char(); // get the replacement char |
3838 | if (*dot != '\n') { | 4237 | if (*dot != '\n') { |
4238 | #if ENABLE_FEATURE_VI_UNDO | ||
4239 | undo_push(dot, 1, UNDO_DEL); | ||
4240 | *dot = c1; | ||
4241 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4242 | #else | ||
3839 | *dot = c1; | 4243 | *dot = c1; |
3840 | file_modified++; | 4244 | modified_count++; |
4245 | #endif | ||
3841 | } | 4246 | } |
3842 | end_cmd_q(); // stop adding to q | 4247 | end_cmd_q(); // stop adding to q |
3843 | break; | 4248 | break; |
@@ -3877,13 +4282,25 @@ static void do_cmd(int c) | |||
3877 | break; | 4282 | break; |
3878 | case '~': // ~- flip the case of letters a-z -> A-Z | 4283 | case '~': // ~- flip the case of letters a-z -> A-Z |
3879 | do { | 4284 | do { |
4285 | #if ENABLE_FEATURE_VI_UNDO | ||
4286 | if (islower(*dot)) { | ||
4287 | undo_push(dot, 1, UNDO_DEL); | ||
4288 | *dot = toupper(*dot); | ||
4289 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4290 | } else if (isupper(*dot)) { | ||
4291 | undo_push(dot, 1, UNDO_DEL); | ||
4292 | *dot = tolower(*dot); | ||
4293 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4294 | } | ||
4295 | #else | ||
3880 | if (islower(*dot)) { | 4296 | if (islower(*dot)) { |
3881 | *dot = toupper(*dot); | 4297 | *dot = toupper(*dot); |
3882 | file_modified++; | 4298 | modified_count++; |
3883 | } else if (isupper(*dot)) { | 4299 | } else if (isupper(*dot)) { |
3884 | *dot = tolower(*dot); | 4300 | *dot = tolower(*dot); |
3885 | file_modified++; | 4301 | modified_count++; |
3886 | } | 4302 | } |
4303 | #endif | ||
3887 | dot_right(); | 4304 | dot_right(); |
3888 | } while (--cmdcnt > 0); | 4305 | } while (--cmdcnt > 0); |
3889 | end_cmd_q(); // stop adding to q | 4306 | end_cmd_q(); // stop adding to q |
@@ -3913,7 +4330,7 @@ static void do_cmd(int c) | |||
3913 | dc1: | 4330 | dc1: |
3914 | // if text[] just became empty, add back an empty line | 4331 | // if text[] just became empty, add back an empty line |
3915 | if (end == text) { | 4332 | if (end == text) { |
3916 | char_insert(text, '\n'); // start empty buf with dummy line | 4333 | char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line |
3917 | dot = text; | 4334 | dot = text; |
3918 | } | 4335 | } |
3919 | // it is OK for dot to exactly equal to end, otherwise check dot validity | 4336 | // it is OK for dot to exactly equal to end, otherwise check dot validity |
diff --git a/include/applets.src.h b/include/applets.src.h index 7dbd4c7f3..cb36628b5 100644 --- a/include/applets.src.h +++ b/include/applets.src.h | |||
@@ -8,7 +8,7 @@ | |||
8 | 8 | ||
9 | /* | 9 | /* |
10 | name - applet name as it is typed on command line | 10 | name - applet name as it is typed on command line |
11 | name2 - applet name, converted to C (ether-wake: name2 = ether_wake) | 11 | help - applet name, converted to C (ether-wake: help = ether_wake) |
12 | main - corresponding <applet>_main to call (bzcat: main = bunzip2) | 12 | main - corresponding <applet>_main to call (bzcat: main = bunzip2) |
13 | l - location to install link to: [/usr]/[s]bin | 13 | l - location to install link to: [/usr]/[s]bin |
14 | s - suid type: | 14 | s - suid type: |
@@ -24,46 +24,46 @@ s - suid type: | |||
24 | 24 | ||
25 | #if defined(PROTOTYPES) | 25 | #if defined(PROTOTYPES) |
26 | # define APPLET(name,l,s) int name##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 26 | # define APPLET(name,l,s) int name##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
27 | # define APPLET_ODDNAME(name,main,l,s,name2) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 27 | # define APPLET_ODDNAME(name,main,l,s,help) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
28 | # define APPLET_NOEXEC(name,main,l,s,name2) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 28 | # define APPLET_NOEXEC(name,main,l,s,help) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
29 | # define APPLET_NOFORK(name,main,l,s,name2) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 29 | # define APPLET_NOFORK(name,main,l,s,help) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
30 | 30 | ||
31 | #elif defined(NAME_MAIN_CNAME) | 31 | #elif defined(NAME_MAIN) |
32 | # define APPLET(name,l,s) name name##_main name | 32 | # define APPLET(name,l,s) name name##_main |
33 | # define APPLET_ODDNAME(name,main,l,s,name2) name main##_main name2 | 33 | # define APPLET_ODDNAME(name,main,l,s,help) name main##_main |
34 | # define APPLET_NOEXEC(name,main,l,s,name2) name main##_main name2 | 34 | # define APPLET_NOEXEC(name,main,l,s,help) name main##_main |
35 | # define APPLET_NOFORK(name,main,l,s,name2) name main##_main name2 | 35 | # define APPLET_NOFORK(name,main,l,s,help) name main##_main |
36 | 36 | ||
37 | #elif defined(MAKE_USAGE) && ENABLE_FEATURE_VERBOSE_USAGE | 37 | #elif defined(MAKE_USAGE) && ENABLE_FEATURE_VERBOSE_USAGE |
38 | # define APPLET(name,l,s) MAKE_USAGE(#name, name##_trivial_usage name##_full_usage) | 38 | # define APPLET(name,l,s) MAKE_USAGE(#name, name##_trivial_usage name##_full_usage) |
39 | # define APPLET_ODDNAME(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage name2##_full_usage) | 39 | # define APPLET_ODDNAME(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage help##_full_usage) |
40 | # define APPLET_NOEXEC(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage name2##_full_usage) | 40 | # define APPLET_NOEXEC(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage help##_full_usage) |
41 | # define APPLET_NOFORK(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage name2##_full_usage) | 41 | # define APPLET_NOFORK(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage help##_full_usage) |
42 | 42 | ||
43 | #elif defined(MAKE_USAGE) && !ENABLE_FEATURE_VERBOSE_USAGE | 43 | #elif defined(MAKE_USAGE) && !ENABLE_FEATURE_VERBOSE_USAGE |
44 | # define APPLET(name,l,s) MAKE_USAGE(#name, name##_trivial_usage) | 44 | # define APPLET(name,l,s) MAKE_USAGE(#name, name##_trivial_usage) |
45 | # define APPLET_ODDNAME(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage) | 45 | # define APPLET_ODDNAME(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage) |
46 | # define APPLET_NOEXEC(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage) | 46 | # define APPLET_NOEXEC(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage) |
47 | # define APPLET_NOFORK(name,main,l,s,name2) MAKE_USAGE(#name, name2##_trivial_usage) | 47 | # define APPLET_NOFORK(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage) |
48 | 48 | ||
49 | #elif defined(MAKE_LINKS) | 49 | #elif defined(MAKE_LINKS) |
50 | # define APPLET(name,l,c) LINK l name | 50 | # define APPLET(name,l,c) LINK l name |
51 | # define APPLET_ODDNAME(name,main,l,s,name2) LINK l name | 51 | # define APPLET_ODDNAME(name,main,l,s,help) LINK l name |
52 | # define APPLET_NOEXEC(name,main,l,s,name2) LINK l name | 52 | # define APPLET_NOEXEC(name,main,l,s,help) LINK l name |
53 | # define APPLET_NOFORK(name,main,l,s,name2) LINK l name | 53 | # define APPLET_NOFORK(name,main,l,s,help) LINK l name |
54 | 54 | ||
55 | #elif defined(MAKE_SUID) | 55 | #elif defined(MAKE_SUID) |
56 | # define APPLET(name,l,s) SUID s l name | 56 | # define APPLET(name,l,s) SUID s l name |
57 | # define APPLET_ODDNAME(name,main,l,s,name2) SUID s l name | 57 | # define APPLET_ODDNAME(name,main,l,s,help) SUID s l name |
58 | # define APPLET_NOEXEC(name,main,l,s,name2) SUID s l name | 58 | # define APPLET_NOEXEC(name,main,l,s,help) SUID s l name |
59 | # define APPLET_NOFORK(name,main,l,s,name2) SUID s l name | 59 | # define APPLET_NOFORK(name,main,l,s,help) SUID s l name |
60 | 60 | ||
61 | #else | 61 | #else |
62 | static struct bb_applet applets[] = { /* name, main, location, need_suid */ | 62 | static struct bb_applet applets[] = { /* name, main, location, need_suid */ |
63 | # define APPLET(name,l,s) { #name, #name, l, s }, | 63 | # define APPLET(name,l,s) { #name, #name, l, s }, |
64 | # define APPLET_ODDNAME(name,main,l,s,name2) { #name, #main, l, s }, | 64 | # define APPLET_ODDNAME(name,main,l,s,help) { #name, #main, l, s }, |
65 | # define APPLET_NOEXEC(name,main,l,s,name2) { #name, #main, l, s, 1 }, | 65 | # define APPLET_NOEXEC(name,main,l,s,help) { #name, #main, l, s, 1 }, |
66 | # define APPLET_NOFORK(name,main,l,s,name2) { #name, #main, l, s, 1, 1 }, | 66 | # define APPLET_NOFORK(name,main,l,s,help) { #name, #main, l, s, 1, 1 }, |
67 | #endif | 67 | #endif |
68 | 68 | ||
69 | #if ENABLE_INSTALL_NO_USR | 69 | #if ENABLE_INSTALL_NO_USR |
@@ -104,7 +104,6 @@ IF_CKSUM(APPLET_NOEXEC(cksum, cksum, BB_DIR_USR_BIN, BB_SUID_DROP, cksum)) | |||
104 | IF_CLEAR(APPLET(clear, BB_DIR_USR_BIN, BB_SUID_DROP)) | 104 | IF_CLEAR(APPLET(clear, BB_DIR_USR_BIN, BB_SUID_DROP)) |
105 | IF_COMM(APPLET(comm, BB_DIR_USR_BIN, BB_SUID_DROP)) | 105 | IF_COMM(APPLET(comm, BB_DIR_USR_BIN, BB_SUID_DROP)) |
106 | IF_CP(APPLET_NOEXEC(cp, cp, BB_DIR_BIN, BB_SUID_DROP, cp)) | 106 | IF_CP(APPLET_NOEXEC(cp, cp, BB_DIR_BIN, BB_SUID_DROP, cp)) |
107 | IF_CROND(APPLET(crond, BB_DIR_USR_SBIN, BB_SUID_DROP)) | ||
108 | /* Needs to be run by root or be suid root - needs to change /var/spool/cron* files: */ | 107 | /* Needs to be run by root or be suid root - needs to change /var/spool/cron* files: */ |
109 | IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE)) | 108 | IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE)) |
110 | IF_CRYPTPW(APPLET(cryptpw, BB_DIR_USR_BIN, BB_SUID_DROP)) | 109 | IF_CRYPTPW(APPLET(cryptpw, BB_DIR_USR_BIN, BB_SUID_DROP)) |
@@ -391,7 +390,7 @@ IF_WHOAMI(APPLET_NOFORK(whoami, whoami, BB_DIR_USR_BIN, BB_SUID_DROP, whoami)) | |||
391 | IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) | 390 | IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) |
392 | IF_ZCIP(APPLET(zcip, BB_DIR_SBIN, BB_SUID_DROP)) | 391 | IF_ZCIP(APPLET(zcip, BB_DIR_SBIN, BB_SUID_DROP)) |
393 | 392 | ||
394 | #if !defined(PROTOTYPES) && !defined(NAME_MAIN_CNAME) && !defined(MAKE_USAGE) \ | 393 | #if !defined(PROTOTYPES) && !defined(NAME_MAIN) && !defined(MAKE_USAGE) \ |
395 | && !defined(MAKE_LINKS) && !defined(MAKE_SUID) | 394 | && !defined(MAKE_LINKS) && !defined(MAKE_SUID) |
396 | }; | 395 | }; |
397 | #endif | 396 | #endif |
diff --git a/include/libbb.h b/include/libbb.h index 3b2bf0a0a..c80cd80d7 100644 --- a/include/libbb.h +++ b/include/libbb.h | |||
@@ -928,9 +928,9 @@ void FAST_FUNC update_utmp(pid_t pid, int new_type, const char *tty_name, const | |||
928 | #endif | 928 | #endif |
929 | 929 | ||
930 | 930 | ||
931 | int execable_file(const char *name) FAST_FUNC; | 931 | int file_is_executable(const char *name) FAST_FUNC; |
932 | char *find_execable(const char *filename, char **PATHp) FAST_FUNC; | 932 | char *find_executable(const char *filename, char **PATHp) FAST_FUNC; |
933 | int exists_execable(const char *filename) FAST_FUNC; | 933 | int executable_exists(const char *filename) FAST_FUNC; |
934 | 934 | ||
935 | /* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff), | 935 | /* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff), |
936 | * but it may exec busybox and call applet instead of searching PATH. | 936 | * but it may exec busybox and call applet instead of searching PATH. |
@@ -1090,6 +1090,7 @@ enum { | |||
1090 | LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, | 1090 | LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, |
1091 | }; | 1091 | }; |
1092 | extern const char *msg_eol; | 1092 | extern const char *msg_eol; |
1093 | extern smallint syslog_level; | ||
1093 | extern smallint logmode; | 1094 | extern smallint logmode; |
1094 | extern int die_sleep; | 1095 | extern int die_sleep; |
1095 | extern uint8_t xfunc_error_retval; | 1096 | extern uint8_t xfunc_error_retval; |
diff --git a/include/platform.h b/include/platform.h index 515ef031f..884c6e95f 100644 --- a/include/platform.h +++ b/include/platform.h | |||
@@ -390,6 +390,7 @@ typedef unsigned smalluint; | |||
390 | #define HAVE_STRSIGNAL 1 | 390 | #define HAVE_STRSIGNAL 1 |
391 | #define HAVE_STRVERSCMP 1 | 391 | #define HAVE_STRVERSCMP 1 |
392 | #define HAVE_VASPRINTF 1 | 392 | #define HAVE_VASPRINTF 1 |
393 | #define HAVE_USLEEP 1 | ||
393 | #define HAVE_UNLOCKED_STDIO 1 | 394 | #define HAVE_UNLOCKED_STDIO 1 |
394 | #define HAVE_UNLOCKED_LINE_OPS 1 | 395 | #define HAVE_UNLOCKED_LINE_OPS 1 |
395 | #define HAVE_GETLINE 1 | 396 | #define HAVE_GETLINE 1 |
@@ -398,8 +399,15 @@ typedef unsigned smalluint; | |||
398 | #define HAVE_NET_ETHERNET_H 1 | 399 | #define HAVE_NET_ETHERNET_H 1 |
399 | #define HAVE_SYS_STATFS_H 1 | 400 | #define HAVE_SYS_STATFS_H 1 |
400 | 401 | ||
401 | #if defined(__UCLIBC__) && UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32) | 402 | #if defined(__UCLIBC__) |
402 | # undef HAVE_STRVERSCMP | 403 | # if UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32) |
404 | # undef HAVE_STRVERSCMP | ||
405 | # endif | ||
406 | # if UCLIBC_VERSION >= KERNEL_VERSION(0, 9, 30) | ||
407 | # ifndef __UCLIBC_SUSV3_LEGACY__ | ||
408 | # undef HAVE_USLEEP | ||
409 | # endif | ||
410 | # endif | ||
403 | #endif | 411 | #endif |
404 | 412 | ||
405 | #if ENABLE_PLATFORM_MINGW32 | 413 | #if ENABLE_PLATFORM_MINGW32 |
@@ -554,6 +562,10 @@ extern char *strsep(char **stringp, const char *delim) FAST_FUNC; | |||
554 | # define strsignal(sig) get_signame(sig) | 562 | # define strsignal(sig) get_signame(sig) |
555 | #endif | 563 | #endif |
556 | 564 | ||
565 | #ifndef HAVE_USLEEP | ||
566 | extern int usleep(unsigned) FAST_FUNC; | ||
567 | #endif | ||
568 | |||
557 | #ifndef HAVE_VASPRINTF | 569 | #ifndef HAVE_VASPRINTF |
558 | # include <stdarg.h> | 570 | # include <stdarg.h> |
559 | extern int vasprintf(char **string_ptr, const char *format, va_list p) FAST_FUNC; | 571 | extern int vasprintf(char **string_ptr, const char *format, va_list p) FAST_FUNC; |
diff --git a/libbb/Kbuild.src b/libbb/Kbuild.src index d81ae8468..107c80689 100644 --- a/libbb/Kbuild.src +++ b/libbb/Kbuild.src | |||
@@ -30,7 +30,7 @@ lib-y += crc32.o | |||
30 | lib-y += default_error_retval.o | 30 | lib-y += default_error_retval.o |
31 | lib-y += device_open.o | 31 | lib-y += device_open.o |
32 | lib-y += dump.o | 32 | lib-y += dump.o |
33 | lib-y += execable.o | 33 | lib-y += executable.o |
34 | lib-y += fclose_nonstdin.o | 34 | lib-y += fclose_nonstdin.o |
35 | lib-y += fflush_stdout_and_exit.o | 35 | lib-y += fflush_stdout_and_exit.o |
36 | lib-y += fgets_str.o | 36 | lib-y += fgets_str.o |
diff --git a/libbb/execable.c b/libbb/executable.c index ae61a8800..2e5f6a1b7 100644 --- a/libbb/execable.c +++ b/libbb/executable.c | |||
@@ -13,7 +13,7 @@ | |||
13 | * return 1 if found; | 13 | * return 1 if found; |
14 | * return 0 otherwise; | 14 | * return 0 otherwise; |
15 | */ | 15 | */ |
16 | int FAST_FUNC execable_file(const char *name) | 16 | int FAST_FUNC file_is_executable(const char *name) |
17 | { | 17 | { |
18 | struct stat s; | 18 | struct stat s; |
19 | return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); | 19 | return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); |
@@ -23,7 +23,7 @@ int FAST_FUNC execable_file(const char *name) | |||
23 | * return allocated string containing full path if found; | 23 | * return allocated string containing full path if found; |
24 | * PATHp points to the component after the one where it was found | 24 | * PATHp points to the component after the one where it was found |
25 | * (or NULL), | 25 | * (or NULL), |
26 | * you may call find_execable again with this PATHp to continue | 26 | * you may call find_executable again with this PATHp to continue |
27 | * (if it's not NULL). | 27 | * (if it's not NULL). |
28 | * return NULL otherwise; (PATHp is undefined) | 28 | * return NULL otherwise; (PATHp is undefined) |
29 | * in all cases (*PATHp) contents will be trashed (s/:/NUL/). | 29 | * in all cases (*PATHp) contents will be trashed (s/:/NUL/). |
@@ -32,8 +32,16 @@ int FAST_FUNC execable_file(const char *name) | |||
32 | #define next_path_sep(s) strchr(s, ':') | 32 | #define next_path_sep(s) strchr(s, ':') |
33 | #endif | 33 | #endif |
34 | 34 | ||
35 | char* FAST_FUNC find_execable(const char *filename, char **PATHp) | 35 | char* FAST_FUNC find_executable(const char *filename, char **PATHp) |
36 | { | 36 | { |
37 | /* About empty components in $PATH: | ||
38 | * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html | ||
39 | * 8.3 Other Environment Variables - PATH | ||
40 | * A zero-length prefix is a legacy feature that indicates the current | ||
41 | * working directory. It appears as two adjacent colons ( "::" ), as an | ||
42 | * initial colon preceding the rest of the list, or as a trailing colon | ||
43 | * following the rest of the list. | ||
44 | */ | ||
37 | char *p, *n; | 45 | char *p, *n; |
38 | #if ENABLE_PLATFORM_MINGW32 | 46 | #if ENABLE_PLATFORM_MINGW32 |
39 | char *w; | 47 | char *w; |
@@ -44,21 +52,22 @@ char* FAST_FUNC find_execable(const char *filename, char **PATHp) | |||
44 | n = (char*)next_path_sep(p); | 52 | n = (char*)next_path_sep(p); |
45 | if (n) | 53 | if (n) |
46 | *n++ = '\0'; | 54 | *n++ = '\0'; |
47 | if (*p != '\0') { /* it's not a PATH="foo::bar" situation */ | 55 | p = concat_path_file( |
48 | p = concat_path_file(p, filename); | 56 | p[0] ? p : ".", /* handle "::" case */ |
49 | if (execable_file(p)) { | 57 | filename |
50 | *PATHp = n; | 58 | ); |
51 | return p; | 59 | if (file_is_executable(p)) { |
52 | } | 60 | *PATHp = n; |
61 | return p; | ||
62 | } | ||
53 | #if ENABLE_PLATFORM_MINGW32 | 63 | #if ENABLE_PLATFORM_MINGW32 |
54 | else if ((w=win32_execable_file(p))) { | 64 | else if ((w=win32_execable_file(p))) { |
55 | *PATHp = n; | 65 | *PATHp = n; |
56 | free(p); | ||
57 | return w; | ||
58 | } | ||
59 | #endif | ||
60 | free(p); | 66 | free(p); |
67 | return w; | ||
61 | } | 68 | } |
69 | #endif | ||
70 | free(p); | ||
62 | p = n; | 71 | p = n; |
63 | } /* on loop exit p == NULL */ | 72 | } /* on loop exit p == NULL */ |
64 | return p; | 73 | return p; |
@@ -68,17 +77,14 @@ char* FAST_FUNC find_execable(const char *filename, char **PATHp) | |||
68 | * return 1 if found; | 77 | * return 1 if found; |
69 | * return 0 otherwise; | 78 | * return 0 otherwise; |
70 | */ | 79 | */ |
71 | int FAST_FUNC exists_execable(const char *filename) | 80 | int FAST_FUNC executable_exists(const char *filename) |
72 | { | 81 | { |
73 | char *path = xstrdup(getenv("PATH")); | 82 | char *path = xstrdup(getenv("PATH")); |
74 | char *tmp = path; | 83 | char *tmp = path; |
75 | char *ret = find_execable(filename, &tmp); | 84 | char *ret = find_executable(filename, &tmp); |
76 | free(path); | 85 | free(path); |
77 | if (ret) { | 86 | free(ret); |
78 | free(ret); | 87 | return ret != NULL; |
79 | return 1; | ||
80 | } | ||
81 | return 0; | ||
82 | } | 88 | } |
83 | 89 | ||
84 | #if ENABLE_FEATURE_PREFER_APPLETS | 90 | #if ENABLE_FEATURE_PREFER_APPLETS |
diff --git a/libbb/getpty.c b/libbb/getpty.c index 435e4d09f..391d729f2 100644 --- a/libbb/getpty.c +++ b/libbb/getpty.c | |||
@@ -16,7 +16,7 @@ int FAST_FUNC xgetpty(char *line) | |||
16 | 16 | ||
17 | #if ENABLE_FEATURE_DEVPTS | 17 | #if ENABLE_FEATURE_DEVPTS |
18 | p = open("/dev/ptmx", O_RDWR); | 18 | p = open("/dev/ptmx", O_RDWR); |
19 | if (p > 0) { | 19 | if (p >= 0) { |
20 | grantpt(p); /* chmod+chown corresponding slave pty */ | 20 | grantpt(p); /* chmod+chown corresponding slave pty */ |
21 | unlockpt(p); /* (what does this do?) */ | 21 | unlockpt(p); /* (what does this do?) */ |
22 | # ifndef HAVE_PTSNAME_R | 22 | # ifndef HAVE_PTSNAME_R |
diff --git a/libbb/obscure.c b/libbb/obscure.c index 9ecc1f672..24c4ac917 100644 --- a/libbb/obscure.c +++ b/libbb/obscure.c | |||
@@ -76,7 +76,7 @@ static int string_checker(const char *p1, const char *p2) | |||
76 | ret |= string_checker_helper(p, p2); | 76 | ret |= string_checker_helper(p, p2); |
77 | 77 | ||
78 | /* clean up */ | 78 | /* clean up */ |
79 | memset(p, 0, size); | 79 | nuke_str(p); |
80 | free(p); | 80 | free(p); |
81 | 81 | ||
82 | return ret; | 82 | return ret; |
diff --git a/libbb/platform.c b/libbb/platform.c index 19734517b..8d90ca4e9 100644 --- a/libbb/platform.c +++ b/libbb/platform.c | |||
@@ -17,6 +17,24 @@ char* FAST_FUNC strchrnul(const char *s, int c) | |||
17 | } | 17 | } |
18 | #endif | 18 | #endif |
19 | 19 | ||
20 | #ifndef HAVE_USLEEP | ||
21 | int FAST_FUNC usleep(unsigned usec) | ||
22 | { | ||
23 | struct timespec ts; | ||
24 | ts.tv_sec = usec / 1000000u; | ||
25 | ts.tv_nsec = (usec % 1000000u) * 1000u; | ||
26 | /* | ||
27 | * If a signal has non-default handler, nanosleep returns early. | ||
28 | * Our version of usleep doesn't return early | ||
29 | * if interrupted by such signals: | ||
30 | * | ||
31 | */ | ||
32 | while (nanosleep(&ts, &ts) != 0) | ||
33 | continue; | ||
34 | return 0; | ||
35 | } | ||
36 | #endif | ||
37 | |||
20 | #ifndef HAVE_VASPRINTF | 38 | #ifndef HAVE_VASPRINTF |
21 | int FAST_FUNC vasprintf(char **string_ptr, const char *format, va_list p) | 39 | int FAST_FUNC vasprintf(char **string_ptr, const char *format, va_list p) |
22 | { | 40 | { |
diff --git a/libbb/rtc.c b/libbb/rtc.c index 97455e86a..6d06d57f9 100644 --- a/libbb/rtc.c +++ b/libbb/rtc.c | |||
@@ -33,23 +33,55 @@ int FAST_FUNC rtc_adjtime_is_utc(void) | |||
33 | return utc; | 33 | return utc; |
34 | } | 34 | } |
35 | 35 | ||
36 | /* rtc opens are exclusive. | ||
37 | * Try to run two "hwclock -w" at the same time to see it. | ||
38 | * Users wouldn't expect that to fail merely because /dev/rtc | ||
39 | * was momentarily busy, let's try a bit harder on errno == EBUSY. | ||
40 | */ | ||
41 | static int open_loop_on_busy(const char *name, int flags) | ||
42 | { | ||
43 | int rtc; | ||
44 | /* | ||
45 | * Tested with two parallel "hwclock -w" loops. | ||
46 | * With try = 10, no failures with 2x1000000 loop iterations. | ||
47 | */ | ||
48 | int try = 1000 / 20; | ||
49 | again: | ||
50 | errno = 0; | ||
51 | rtc = open(name, flags); | ||
52 | if (errno == EBUSY) { | ||
53 | usleep(20 * 1000); | ||
54 | if (--try != 0) | ||
55 | goto again; | ||
56 | /* EBUSY. Last try, exit on error instead of returning -1 */ | ||
57 | return xopen(name, flags); | ||
58 | } | ||
59 | return rtc; | ||
60 | } | ||
61 | |||
62 | /* Never fails */ | ||
36 | int FAST_FUNC rtc_xopen(const char **default_rtc, int flags) | 63 | int FAST_FUNC rtc_xopen(const char **default_rtc, int flags) |
37 | { | 64 | { |
38 | int rtc; | 65 | int rtc; |
66 | const char *name = | ||
67 | "/dev/rtc""\0" | ||
68 | "/dev/rtc0""\0" | ||
69 | "/dev/misc/rtc""\0"; | ||
39 | 70 | ||
40 | if (!*default_rtc) { | 71 | if (!*default_rtc) |
41 | *default_rtc = "/dev/rtc"; | 72 | goto try_name; |
42 | rtc = open(*default_rtc, flags); | 73 | name = ""; /*else: we have rtc name, don't try other names */ |
43 | if (rtc >= 0) | 74 | |
44 | return rtc; | 75 | for (;;) { |
45 | *default_rtc = "/dev/rtc0"; | 76 | rtc = open_loop_on_busy(*default_rtc, flags); |
46 | rtc = open(*default_rtc, flags); | ||
47 | if (rtc >= 0) | 77 | if (rtc >= 0) |
48 | return rtc; | 78 | return rtc; |
49 | *default_rtc = "/dev/misc/rtc"; | 79 | if (!name[0]) |
80 | return xopen(*default_rtc, flags); | ||
81 | try_name: | ||
82 | *default_rtc = name; | ||
83 | name += strlen(name) + 1; | ||
50 | } | 84 | } |
51 | |||
52 | return xopen(*default_rtc, flags); | ||
53 | } | 85 | } |
54 | 86 | ||
55 | void FAST_FUNC rtc_read_tm(struct tm *ptm, int fd) | 87 | void FAST_FUNC rtc_read_tm(struct tm *ptm, int fd) |
diff --git a/libbb/verror_msg.c b/libbb/verror_msg.c index ee95be3e3..0ef2a311f 100644 --- a/libbb/verror_msg.c +++ b/libbb/verror_msg.c | |||
@@ -11,6 +11,9 @@ | |||
11 | # include <syslog.h> | 11 | # include <syslog.h> |
12 | #endif | 12 | #endif |
13 | 13 | ||
14 | #if ENABLE_FEATURE_SYSLOG | ||
15 | smallint syslog_level = LOG_ERR; | ||
16 | #endif | ||
14 | smallint logmode = LOGMODE_STDIO; | 17 | smallint logmode = LOGMODE_STDIO; |
15 | const char *msg_eol = "\n"; | 18 | const char *msg_eol = "\n"; |
16 | 19 | ||
@@ -70,7 +73,7 @@ void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr) | |||
70 | } | 73 | } |
71 | #if ENABLE_FEATURE_SYSLOG | 74 | #if ENABLE_FEATURE_SYSLOG |
72 | if (logmode & LOGMODE_SYSLOG) { | 75 | if (logmode & LOGMODE_SYSLOG) { |
73 | syslog(LOG_ERR, "%s", msg + applet_len); | 76 | syslog(syslog_level, "%s", msg + applet_len); |
74 | } | 77 | } |
75 | #endif | 78 | #endif |
76 | free(msg); | 79 | free(msg); |
diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c index ea09f20b8..2c5a9ef39 100644 --- a/libbb/xreadlink.c +++ b/libbb/xreadlink.c | |||
@@ -1,14 +1,14 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | 1 | /* vi: set sw=4 ts=4: */ |
2 | /* | 2 | /* |
3 | * xreadlink.c - safe implementation of readlink. | 3 | * xreadlink.c - safe implementation of readlink. |
4 | * Returns a NULL on failure... | 4 | * Returns a NULL on failure. |
5 | * | 5 | * |
6 | * Licensed under GPLv2, see file LICENSE in this source tree. | 6 | * Licensed under GPLv2, see file LICENSE in this source tree. |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include "libbb.h" | 9 | #include "libbb.h" |
10 | 10 | ||
11 | /* some systems (eg Hurd) does not have MAXSYMLINKS definition, | 11 | /* Some systems (eg Hurd) do not have MAXSYMLINKS definition, |
12 | * set it to some reasonable value if it isn't defined */ | 12 | * set it to some reasonable value if it isn't defined */ |
13 | #ifndef MAXSYMLINKS | 13 | #ifndef MAXSYMLINKS |
14 | # define MAXSYMLINKS 20 | 14 | # define MAXSYMLINKS 20 |
@@ -108,8 +108,11 @@ char* FAST_FUNC xmalloc_readlink_or_warn(const char *path) | |||
108 | 108 | ||
109 | char* FAST_FUNC xmalloc_realpath(const char *path) | 109 | char* FAST_FUNC xmalloc_realpath(const char *path) |
110 | { | 110 | { |
111 | #if defined(__GLIBC__) || \ | 111 | /* NB: uclibc also defines __GLIBC__ |
112 | (defined(__UCLIBC__) && UCLIBC_VERSION >= KERNEL_VERSION(0, 9, 31)) | 112 | * Therefore the test "if glibc, or uclibc >= 0.9.31" looks a bit weird: |
113 | */ | ||
114 | #if defined(__GLIBC__) && \ | ||
115 | (!defined(__UCLIBC__) || UCLIBC_VERSION >= KERNEL_VERSION(0, 9, 31)) | ||
113 | /* glibc provides a non-standard extension */ | 116 | /* glibc provides a non-standard extension */ |
114 | /* new: POSIX.1-2008 specifies this behavior as well */ | 117 | /* new: POSIX.1-2008 specifies this behavior as well */ |
115 | return realpath(path, NULL); | 118 | return realpath(path, NULL); |
diff --git a/loginutils/su.c b/loginutils/su.c index c51f26f70..f8125054a 100644 --- a/loginutils/su.c +++ b/loginutils/su.c | |||
@@ -101,6 +101,7 @@ int su_main(int argc UNUSED_PARAM, char **argv) | |||
101 | if (ENABLE_FEATURE_SU_SYSLOG) | 101 | if (ENABLE_FEATURE_SU_SYSLOG) |
102 | syslog(LOG_NOTICE, "%c %s %s:%s", | 102 | syslog(LOG_NOTICE, "%c %s %s:%s", |
103 | '-', tty, old_user, opt_username); | 103 | '-', tty, old_user, opt_username); |
104 | bb_do_delay(LOGIN_FAIL_DELAY); | ||
104 | bb_error_msg_and_die("incorrect password"); | 105 | bb_error_msg_and_die("incorrect password"); |
105 | } | 106 | } |
106 | 107 | ||
diff --git a/miscutils/Config.src b/miscutils/Config.src index 1da9800bd..1b2a3ae9a 100644 --- a/miscutils/Config.src +++ b/miscutils/Config.src | |||
@@ -133,40 +133,6 @@ config CHRT | |||
133 | manipulate real-time attributes of a process. | 133 | manipulate real-time attributes of a process. |
134 | This requires sched_{g,s}etparam support in your libc. | 134 | This requires sched_{g,s}etparam support in your libc. |
135 | 135 | ||
136 | config CROND | ||
137 | bool "crond" | ||
138 | default y | ||
139 | select FEATURE_SYSLOG | ||
140 | help | ||
141 | Crond is a background daemon that parses individual crontab | ||
142 | files and executes commands on behalf of the users in question. | ||
143 | This is a port of dcron from slackware. It uses files of the | ||
144 | format /var/spool/cron/crontabs/<username> files, for example: | ||
145 | $ cat /var/spool/cron/crontabs/root | ||
146 | # Run daily cron jobs at 4:40 every day: | ||
147 | 40 4 * * * /etc/cron/daily > /dev/null 2>&1 | ||
148 | |||
149 | config FEATURE_CROND_D | ||
150 | bool "Support option -d to redirect output to stderr" | ||
151 | depends on CROND | ||
152 | default y | ||
153 | help | ||
154 | -d sets loglevel to 0 (most verbose) and directs all output to stderr. | ||
155 | |||
156 | config FEATURE_CROND_CALL_SENDMAIL | ||
157 | bool "Report command output via email (using sendmail)" | ||
158 | default y | ||
159 | depends on CROND | ||
160 | help | ||
161 | Command output will be sent to corresponding user via email. | ||
162 | |||
163 | config FEATURE_CROND_DIR | ||
164 | string "crond spool directory" | ||
165 | default "/var/spool/cron" | ||
166 | depends on CROND || CRONTAB | ||
167 | help | ||
168 | Location of crond spool. | ||
169 | |||
170 | config CRONTAB | 136 | config CRONTAB |
171 | bool "crontab" | 137 | bool "crontab" |
172 | default y | 138 | default y |
diff --git a/miscutils/Kbuild.src b/miscutils/Kbuild.src index 9e164f16e..8eaa82de9 100644 --- a/miscutils/Kbuild.src +++ b/miscutils/Kbuild.src | |||
@@ -12,7 +12,6 @@ lib-$(CONFIG_BBCONFIG) += bbconfig.o | |||
12 | lib-$(CONFIG_BEEP) += beep.o | 12 | lib-$(CONFIG_BEEP) += beep.o |
13 | lib-$(CONFIG_CHAT) += chat.o | 13 | lib-$(CONFIG_CHAT) += chat.o |
14 | lib-$(CONFIG_CHRT) += chrt.o | 14 | lib-$(CONFIG_CHRT) += chrt.o |
15 | lib-$(CONFIG_CROND) += crond.o | ||
16 | lib-$(CONFIG_CRONTAB) += crontab.o | 15 | lib-$(CONFIG_CRONTAB) += crontab.o |
17 | lib-$(CONFIG_DC) += dc.o | 16 | lib-$(CONFIG_DC) += dc.o |
18 | lib-$(CONFIG_DEVFSD) += devfsd.o | 17 | lib-$(CONFIG_DEVFSD) += devfsd.o |
diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c index c8816e9e7..534364a69 100644 --- a/miscutils/adjtimex.c +++ b/miscutils/adjtimex.c | |||
@@ -14,12 +14,12 @@ | |||
14 | //usage:#define adjtimex_trivial_usage | 14 | //usage:#define adjtimex_trivial_usage |
15 | //usage: "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]" | 15 | //usage: "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]" |
16 | //usage:#define adjtimex_full_usage "\n\n" | 16 | //usage:#define adjtimex_full_usage "\n\n" |
17 | //usage: "Read and optionally set system timebase parameters. See adjtimex(2)\n" | 17 | //usage: "Read or set kernel time variables. See adjtimex(2)\n" |
18 | //usage: "\n -q Quiet" | 18 | //usage: "\n -q Quiet" |
19 | //usage: "\n -o OFF Time offset, microseconds" | 19 | //usage: "\n -o OFF Time offset, microseconds" |
20 | //usage: "\n -f FREQ Frequency adjust, integer kernel units (65536 is 1ppm)" | 20 | //usage: "\n -f FREQ Frequency adjust, integer kernel units (65536 is 1ppm)" |
21 | //usage: "\n (positive values make clock run faster)" | ||
22 | //usage: "\n -t TICK Microseconds per tick, usually 10000" | 21 | //usage: "\n -t TICK Microseconds per tick, usually 10000" |
22 | //usage: "\n (positive -t or -f values make clock run faster)" | ||
23 | //usage: "\n -p TCONST" | 23 | //usage: "\n -p TCONST" |
24 | 24 | ||
25 | #include "libbb.h" | 25 | #include "libbb.h" |
@@ -111,13 +111,13 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv) | |||
111 | } | 111 | } |
112 | 112 | ||
113 | if (!(opt & OPT_quiet)) { | 113 | if (!(opt & OPT_quiet)) { |
114 | int sep; | 114 | const char *sep; |
115 | const char *name; | 115 | const char *name; |
116 | 116 | ||
117 | printf( | 117 | printf( |
118 | " mode: %d\n" | 118 | " mode: %d\n" |
119 | "-o offset: %ld\n" | 119 | "-o offset: %ld us\n" |
120 | "-f frequency: %ld\n" | 120 | "-f freq.adjust: %ld (65536 = 1ppm)\n" |
121 | " maxerror: %ld\n" | 121 | " maxerror: %ld\n" |
122 | " esterror: %ld\n" | 122 | " esterror: %ld\n" |
123 | " status: %d (", | 123 | " status: %d (", |
@@ -125,15 +125,14 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv) | |||
125 | txc.esterror, txc.status); | 125 | txc.esterror, txc.status); |
126 | 126 | ||
127 | /* representative output of next code fragment: | 127 | /* representative output of next code fragment: |
128 | "PLL | PPSTIME" */ | 128 | * "PLL | PPSTIME" |
129 | */ | ||
129 | name = statlist_name; | 130 | name = statlist_name; |
130 | sep = 0; | 131 | sep = ""; |
131 | for (i = 0; statlist_bit[i]; i++) { | 132 | for (i = 0; statlist_bit[i]; i++) { |
132 | if (txc.status & statlist_bit[i]) { | 133 | if (txc.status & statlist_bit[i]) { |
133 | if (sep) | 134 | printf("%s%s", sep, name); |
134 | fputs(" | ", stdout); | 135 | sep = " | "; |
135 | fputs(name, stdout); | ||
136 | sep = 1; | ||
137 | } | 136 | } |
138 | name += strlen(name) + 1; | 137 | name += strlen(name) + 1; |
139 | } | 138 | } |
@@ -143,9 +142,9 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv) | |||
143 | descript = nth_string(ret_code_descript, ret); | 142 | descript = nth_string(ret_code_descript, ret); |
144 | printf(")\n" | 143 | printf(")\n" |
145 | "-p timeconstant: %ld\n" | 144 | "-p timeconstant: %ld\n" |
146 | " precision: %ld\n" | 145 | " precision: %ld us\n" |
147 | " tolerance: %ld\n" | 146 | " tolerance: %ld\n" |
148 | "-t tick: %ld\n" | 147 | "-t tick: %ld us\n" |
149 | " time.tv_sec: %ld\n" | 148 | " time.tv_sec: %ld\n" |
150 | " time.tv_usec: %ld\n" | 149 | " time.tv_usec: %ld\n" |
151 | " return value: %d (%s)\n", | 150 | " return value: %d (%s)\n", |
diff --git a/miscutils/crond.c b/miscutils/crond.c index 582dc991a..3659b9a6f 100644 --- a/miscutils/crond.c +++ b/miscutils/crond.c | |||
@@ -1,7 +1,5 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | 1 | /* vi: set sw=4 ts=4: */ |
2 | /* | 2 | /* |
3 | * crond -d[#] -c <crondir> -f -b | ||
4 | * | ||
5 | * run as root, but NOT setuid root | 3 | * run as root, but NOT setuid root |
6 | * | 4 | * |
7 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) | 5 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) |
@@ -10,6 +8,43 @@ | |||
10 | * | 8 | * |
11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | 9 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
12 | */ | 10 | */ |
11 | //config:config CROND | ||
12 | //config: bool "crond" | ||
13 | //config: default y | ||
14 | //config: select FEATURE_SYSLOG | ||
15 | //config: help | ||
16 | //config: Crond is a background daemon that parses individual crontab | ||
17 | //config: files and executes commands on behalf of the users in question. | ||
18 | //config: This is a port of dcron from slackware. It uses files of the | ||
19 | //config: format /var/spool/cron/crontabs/<username> files, for example: | ||
20 | //config: $ cat /var/spool/cron/crontabs/root | ||
21 | //config: # Run daily cron jobs at 4:40 every day: | ||
22 | //config: 40 4 * * * /etc/cron/daily > /dev/null 2>&1 | ||
23 | //config: | ||
24 | //config:config FEATURE_CROND_D | ||
25 | //config: bool "Support option -d to redirect output to stderr" | ||
26 | //config: depends on CROND | ||
27 | //config: default y | ||
28 | //config: help | ||
29 | //config: -d N sets loglevel (0:most verbose) and directs all output to stderr. | ||
30 | //config: | ||
31 | //config:config FEATURE_CROND_CALL_SENDMAIL | ||
32 | //config: bool "Report command output via email (using sendmail)" | ||
33 | //config: default y | ||
34 | //config: depends on CROND | ||
35 | //config: help | ||
36 | //config: Command output will be sent to corresponding user via email. | ||
37 | //config: | ||
38 | //config:config FEATURE_CROND_DIR | ||
39 | //config: string "crond spool directory" | ||
40 | //config: default "/var/spool/cron" | ||
41 | //config: depends on CROND || CRONTAB | ||
42 | //config: help | ||
43 | //config: Location of crond spool. | ||
44 | |||
45 | //applet:IF_CROND(APPLET(crond, BB_DIR_USR_SBIN, BB_SUID_DROP)) | ||
46 | |||
47 | //kbuild:lib-$(CONFIG_CROND) += crond.o | ||
13 | 48 | ||
14 | //usage:#define crond_trivial_usage | 49 | //usage:#define crond_trivial_usage |
15 | //usage: "-fbS -l N " IF_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR" | 50 | //usage: "-fbS -l N " IF_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR" |
@@ -17,12 +52,12 @@ | |||
17 | //usage: " -f Foreground" | 52 | //usage: " -f Foreground" |
18 | //usage: "\n -b Background (default)" | 53 | //usage: "\n -b Background (default)" |
19 | //usage: "\n -S Log to syslog (default)" | 54 | //usage: "\n -S Log to syslog (default)" |
20 | //usage: "\n -l Set log level. 0 is the most verbose, default 8" | 55 | //usage: "\n -l N Set log level. Most verbose:0, default:8" |
21 | //usage: IF_FEATURE_CROND_D( | 56 | //usage: IF_FEATURE_CROND_D( |
22 | //usage: "\n -d Set log level, log to stderr" | 57 | //usage: "\n -d N Set log level, log to stderr" |
23 | //usage: ) | 58 | //usage: ) |
24 | //usage: "\n -L Log to file" | 59 | //usage: "\n -L FILE Log to FILE" |
25 | //usage: "\n -c Working dir" | 60 | //usage: "\n -c DIR Cron dir. Default:"CONFIG_FEATURE_CROND_DIR"/crontabs" |
26 | 61 | ||
27 | #include "libbb.h" | 62 | #include "libbb.h" |
28 | #include <syslog.h> | 63 | #include <syslog.h> |
@@ -36,7 +71,7 @@ | |||
36 | #endif | 71 | #endif |
37 | 72 | ||
38 | 73 | ||
39 | #define TMPDIR CONFIG_FEATURE_CROND_DIR | 74 | #define CRON_DIR CONFIG_FEATURE_CROND_DIR |
40 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" | 75 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" |
41 | #ifndef SENDMAIL | 76 | #ifndef SENDMAIL |
42 | # define SENDMAIL "sendmail" | 77 | # define SENDMAIL "sendmail" |
@@ -69,6 +104,7 @@ typedef struct CronLine { | |||
69 | int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ | 104 | int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ |
70 | char *cl_mailto; /* whom to mail results, may be NULL */ | 105 | char *cl_mailto; /* whom to mail results, may be NULL */ |
71 | #endif | 106 | #endif |
107 | char *cl_shell; | ||
72 | /* ordered by size, not in natural order. makes code smaller: */ | 108 | /* ordered by size, not in natural order. makes code smaller: */ |
73 | char cl_Dow[7]; /* 0-6, beginning sunday */ | 109 | char cl_Dow[7]; /* 0-6, beginning sunday */ |
74 | char cl_Mons[12]; /* 0-11 */ | 110 | char cl_Mons[12]; /* 0-11 */ |
@@ -90,12 +126,6 @@ enum { | |||
90 | OPT_c = (1 << 5), | 126 | OPT_c = (1 << 5), |
91 | OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, | 127 | OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, |
92 | }; | 128 | }; |
93 | #if ENABLE_FEATURE_CROND_D | ||
94 | # define DebugOpt (option_mask32 & OPT_d) | ||
95 | #else | ||
96 | # define DebugOpt 0 | ||
97 | #endif | ||
98 | |||
99 | 129 | ||
100 | struct globals { | 130 | struct globals { |
101 | unsigned log_level; /* = 8; */ | 131 | unsigned log_level; /* = 8; */ |
@@ -106,6 +136,8 @@ struct globals { | |||
106 | #if SETENV_LEAKS | 136 | #if SETENV_LEAKS |
107 | char *env_var_user; | 137 | char *env_var_user; |
108 | char *env_var_home; | 138 | char *env_var_home; |
139 | char *env_var_shell; | ||
140 | char *env_var_logname; | ||
109 | #endif | 141 | #endif |
110 | } FIX_ALIASING; | 142 | } FIX_ALIASING; |
111 | #define G (*(struct globals*)&bb_common_bufsiz1) | 143 | #define G (*(struct globals*)&bb_common_bufsiz1) |
@@ -114,56 +146,56 @@ struct globals { | |||
114 | G.crontab_dir_name = CRONTABS; \ | 146 | G.crontab_dir_name = CRONTABS; \ |
115 | } while (0) | 147 | } while (0) |
116 | 148 | ||
149 | /* Log levels: | ||
150 | * 0 is the most verbose, default 8. | ||
151 | * For some reason, in fact only 5, 7 and 8 are used. | ||
152 | */ | ||
153 | static void crondlog(unsigned level, const char *msg, va_list va) | ||
154 | { | ||
155 | if (level >= G.log_level) { | ||
156 | /* | ||
157 | * We are called only for info meesages. | ||
158 | * Warnings/errors use plain bb_[p]error_msg's, which | ||
159 | * need not touch syslog_level | ||
160 | * (they are ok with LOG_ERR default). | ||
161 | */ | ||
162 | syslog_level = LOG_INFO; | ||
163 | bb_verror_msg(msg, va, /* strerr: */ NULL); | ||
164 | syslog_level = LOG_ERR; | ||
165 | } | ||
166 | } | ||
117 | 167 | ||
118 | /* 0 is the most verbose, default 8 */ | 168 | static void log5(const char *msg, ...) |
119 | #define LVL5 "\x05" | 169 | { |
120 | #define LVL7 "\x07" | 170 | va_list va; |
121 | #define LVL8 "\x08" | 171 | va_start(va, msg); |
122 | #define WARN9 "\x49" | 172 | crondlog(4, msg, va); |
123 | #define DIE9 "\xc9" | 173 | va_end(va); |
124 | /* level >= 20 is "error" */ | 174 | } |
125 | #define ERR20 "\x14" | ||
126 | 175 | ||
127 | static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2))); | 176 | static void log7(const char *msg, ...) |
128 | static void crondlog(const char *ctl, ...) | ||
129 | { | 177 | { |
130 | va_list va; | 178 | va_list va; |
131 | int level = (ctl[0] & 0x1f); | 179 | va_start(va, msg); |
132 | 180 | crondlog(7, msg, va); | |
133 | va_start(va, ctl); | 181 | va_end(va); |
134 | if (level >= (int)G.log_level) { | 182 | } |
135 | /* Debug mode: all to (non-redirected) stderr, */ | 183 | |
136 | /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ | 184 | static void log8(const char *msg, ...) |
137 | if (!DebugOpt && G.log_filename) { | 185 | { |
138 | /* Otherwise (log to file): we reopen log file at every write: */ | 186 | va_list va; |
139 | int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND); | 187 | va_start(va, msg); |
140 | if (logfd >= 0) | 188 | crondlog(8, msg, va); |
141 | xmove_fd(logfd, STDERR_FILENO); | ||
142 | } | ||
143 | /* When we log to syslog, level > 8 is logged at LOG_ERR | ||
144 | * syslog level, level <= 8 is logged at LOG_INFO. */ | ||
145 | if (level > 8) { | ||
146 | bb_verror_msg(ctl + 1, va, /* strerr: */ NULL); | ||
147 | } else { | ||
148 | char *msg = NULL; | ||
149 | vasprintf(&msg, ctl + 1, va); | ||
150 | bb_info_msg("%s: %s", applet_name, msg); | ||
151 | free(msg); | ||
152 | } | ||
153 | } | ||
154 | va_end(va); | 189 | va_end(va); |
155 | if (ctl[0] & 0x80) | ||
156 | exit(20); | ||
157 | } | 190 | } |
158 | 191 | ||
192 | |||
159 | static const char DowAry[] ALIGN1 = | 193 | static const char DowAry[] ALIGN1 = |
160 | "sun""mon""tue""wed""thu""fri""sat" | 194 | "sun""mon""tue""wed""thu""fri""sat" |
161 | /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */ | ||
162 | ; | 195 | ; |
163 | 196 | ||
164 | static const char MonAry[] ALIGN1 = | 197 | static const char MonAry[] ALIGN1 = |
165 | "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec" | 198 | "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec" |
166 | /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */ | ||
167 | ; | 199 | ; |
168 | 200 | ||
169 | static void ParseField(char *user, char *ary, int modvalue, int off, | 201 | static void ParseField(char *user, char *ary, int modvalue, int off, |
@@ -267,12 +299,12 @@ static void ParseField(char *user, char *ary, int modvalue, int off, | |||
267 | 299 | ||
268 | if (*ptr) { | 300 | if (*ptr) { |
269 | err: | 301 | err: |
270 | crondlog(WARN9 "user %s: parse error at %s", user, base); | 302 | bb_error_msg("user %s: parse error at %s", user, base); |
271 | return; | 303 | return; |
272 | } | 304 | } |
273 | 305 | ||
274 | if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */ | 306 | /* can't use log5 (it inserts newlines), open-coding it */ |
275 | /* can't use crondlog, it inserts '\n' */ | 307 | if (G.log_level <= 5 && logmode != LOGMODE_SYSLOG) { |
276 | int i; | 308 | int i; |
277 | for (i = 0; i < modvalue; ++i) | 309 | for (i = 0; i < modvalue; ++i) |
278 | fprintf(stderr, "%d", (unsigned char)ary[i]); | 310 | fprintf(stderr, "%d", (unsigned char)ary[i]); |
@@ -368,11 +400,12 @@ static void load_crontab(const char *fileName) | |||
368 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 400 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
369 | char *mailTo = NULL; | 401 | char *mailTo = NULL; |
370 | #endif | 402 | #endif |
403 | char *shell = NULL; | ||
371 | 404 | ||
372 | delete_cronfile(fileName); | 405 | delete_cronfile(fileName); |
373 | 406 | ||
374 | if (!getpwnam(fileName)) { | 407 | if (!getpwnam(fileName)) { |
375 | crondlog(LVL7 "ignoring file '%s' (no such user)", fileName); | 408 | log7("ignoring file '%s' (no such user)", fileName); |
376 | return; | 409 | return; |
377 | } | 410 | } |
378 | 411 | ||
@@ -393,14 +426,16 @@ static void load_crontab(const char *fileName) | |||
393 | while (1) { | 426 | while (1) { |
394 | CronLine *line; | 427 | CronLine *line; |
395 | 428 | ||
396 | if (!--maxLines) | 429 | if (!--maxLines) { |
430 | bb_error_msg("user %s: too many lines", fileName); | ||
397 | break; | 431 | break; |
432 | } | ||
433 | |||
398 | n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); | 434 | n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); |
399 | if (!n) | 435 | if (!n) |
400 | break; | 436 | break; |
401 | 437 | ||
402 | if (DebugOpt) | 438 | log5("user:%s entry:%s", fileName, parser->data); |
403 | crondlog(LVL5 "user:%s entry:%s", fileName, parser->data); | ||
404 | 439 | ||
405 | /* check if line is setting MAILTO= */ | 440 | /* check if line is setting MAILTO= */ |
406 | if (0 == strncmp(tokens[0], "MAILTO=", 7)) { | 441 | if (0 == strncmp(tokens[0], "MAILTO=", 7)) { |
@@ -410,6 +445,24 @@ static void load_crontab(const char *fileName) | |||
410 | #endif /* otherwise just ignore such lines */ | 445 | #endif /* otherwise just ignore such lines */ |
411 | continue; | 446 | continue; |
412 | } | 447 | } |
448 | if (0 == strncmp(tokens[0], "SHELL=", 6)) { | ||
449 | free(shell); | ||
450 | shell = xstrdup(&tokens[0][6]); | ||
451 | continue; | ||
452 | } | ||
453 | //TODO: handle HOME= too? "man crontab" says: | ||
454 | //name = value | ||
455 | // | ||
456 | //where the spaces around the equal-sign (=) are optional, and any subsequent | ||
457 | //non-leading spaces in value will be part of the value assigned to name. | ||
458 | //The value string may be placed in quotes (single or double, but matching) | ||
459 | //to preserve leading or trailing blanks. | ||
460 | // | ||
461 | //Several environment variables are set up automatically by the cron(8) daemon. | ||
462 | //SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd | ||
463 | //line of the crontab's owner. HOME and SHELL may be overridden by settings | ||
464 | //in the crontab; LOGNAME may not. | ||
465 | |||
413 | /* check if a minimum of tokens is specified */ | 466 | /* check if a minimum of tokens is specified */ |
414 | if (n < 6) | 467 | if (n < 6) |
415 | continue; | 468 | continue; |
@@ -429,11 +482,9 @@ static void load_crontab(const char *fileName) | |||
429 | /* copy mailto (can be NULL) */ | 482 | /* copy mailto (can be NULL) */ |
430 | line->cl_mailto = xstrdup(mailTo); | 483 | line->cl_mailto = xstrdup(mailTo); |
431 | #endif | 484 | #endif |
485 | line->cl_shell = xstrdup(shell); | ||
432 | /* copy command */ | 486 | /* copy command */ |
433 | line->cl_cmd = xstrdup(tokens[5]); | 487 | line->cl_cmd = xstrdup(tokens[5]); |
434 | if (DebugOpt) { | ||
435 | crondlog(LVL5 " command:%s", tokens[5]); | ||
436 | } | ||
437 | pline = &line->cl_next; | 488 | pline = &line->cl_next; |
438 | //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); | 489 | //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); |
439 | } | 490 | } |
@@ -441,12 +492,12 @@ static void load_crontab(const char *fileName) | |||
441 | 492 | ||
442 | file->cf_next = G.cron_files; | 493 | file->cf_next = G.cron_files; |
443 | G.cron_files = file; | 494 | G.cron_files = file; |
444 | |||
445 | if (maxLines == 0) { | ||
446 | crondlog(WARN9 "user %s: too many lines", fileName); | ||
447 | } | ||
448 | } | 495 | } |
449 | config_close(parser); | 496 | config_close(parser); |
497 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
498 | free(mailTo); | ||
499 | #endif | ||
500 | free(shell); | ||
450 | } | 501 | } |
451 | 502 | ||
452 | static void process_cron_update_file(void) | 503 | static void process_cron_update_file(void) |
@@ -482,17 +533,16 @@ static void rescan_crontab_dir(void) | |||
482 | /* Remove cron update file */ | 533 | /* Remove cron update file */ |
483 | unlink(CRONUPDATE); | 534 | unlink(CRONUPDATE); |
484 | /* Re-chdir, in case directory was renamed & deleted */ | 535 | /* Re-chdir, in case directory was renamed & deleted */ |
485 | if (chdir(G.crontab_dir_name) < 0) { | 536 | xchdir(G.crontab_dir_name); |
486 | crondlog(DIE9 "chdir(%s)", G.crontab_dir_name); | ||
487 | } | ||
488 | 537 | ||
489 | /* Scan directory and add associated users */ | 538 | /* Scan directory and add associated users */ |
490 | { | 539 | { |
491 | DIR *dir = opendir("."); | 540 | DIR *dir = opendir("."); |
492 | struct dirent *den; | 541 | struct dirent *den; |
493 | 542 | ||
543 | /* xopendir exists, but "can't open '.'" is not informative */ | ||
494 | if (!dir) | 544 | if (!dir) |
495 | crondlog(DIE9 "chdir(%s)", "."); /* exits */ | 545 | bb_error_msg_and_die("can't open '%s'", G.crontab_dir_name); |
496 | while ((den = readdir(dir)) != NULL) { | 546 | while ((den = readdir(dir)) != NULL) { |
497 | if (strchr(den->d_name, '.') != NULL) { | 547 | if (strchr(den->d_name, '.') != NULL) { |
498 | continue; | 548 | continue; |
@@ -519,19 +569,22 @@ static void safe_setenv(char **pvar_val, const char *var, const char *val) | |||
519 | } | 569 | } |
520 | #endif | 570 | #endif |
521 | 571 | ||
522 | static void set_env_vars(struct passwd *pas) | 572 | static void set_env_vars(struct passwd *pas, const char *shell) |
523 | { | 573 | { |
574 | /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL. | ||
575 | * We assume crond inherited suitable PATH. | ||
576 | */ | ||
524 | #if SETENV_LEAKS | 577 | #if SETENV_LEAKS |
578 | safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name); | ||
525 | safe_setenv(&G.env_var_user, "USER", pas->pw_name); | 579 | safe_setenv(&G.env_var_user, "USER", pas->pw_name); |
526 | safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); | 580 | safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); |
527 | /* if we want to set user's shell instead: */ | 581 | safe_setenv(&G.env_var_shell, "SHELL", shell); |
528 | /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/ | ||
529 | #else | 582 | #else |
583 | xsetenv("LOGNAME", pas->pw_name); | ||
530 | xsetenv("USER", pas->pw_name); | 584 | xsetenv("USER", pas->pw_name); |
531 | xsetenv("HOME", pas->pw_dir); | 585 | xsetenv("HOME", pas->pw_dir); |
586 | xsetenv("SHELL", shell); | ||
532 | #endif | 587 | #endif |
533 | /* currently, we use constant one: */ | ||
534 | /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */ | ||
535 | } | 588 | } |
536 | 589 | ||
537 | static void change_user(struct passwd *pas) | 590 | static void change_user(struct passwd *pas) |
@@ -539,10 +592,8 @@ static void change_user(struct passwd *pas) | |||
539 | /* careful: we're after vfork! */ | 592 | /* careful: we're after vfork! */ |
540 | change_identity(pas); /* - initgroups, setgid, setuid */ | 593 | change_identity(pas); /* - initgroups, setgid, setuid */ |
541 | if (chdir(pas->pw_dir) < 0) { | 594 | if (chdir(pas->pw_dir) < 0) { |
542 | crondlog(WARN9 "chdir(%s)", pas->pw_dir); | 595 | bb_error_msg("can't change directory to '%s'", pas->pw_dir); |
543 | if (chdir(TMPDIR) < 0) { | 596 | xchdir(CRON_DIR); |
544 | crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */ | ||
545 | } | ||
546 | } | 597 | } |
547 | } | 598 | } |
548 | 599 | ||
@@ -550,46 +601,53 @@ static void change_user(struct passwd *pas) | |||
550 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 601 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
551 | 602 | ||
552 | static pid_t | 603 | static pid_t |
553 | fork_job(const char *user, int mailFd, | 604 | fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail) |
554 | const char *prog, | 605 | { |
555 | const char *shell_cmd /* if NULL, we run sendmail */ | ||
556 | ) { | ||
557 | struct passwd *pas; | 606 | struct passwd *pas; |
607 | const char *shell, *prog; | ||
608 | smallint sv_logmode; | ||
558 | pid_t pid; | 609 | pid_t pid; |
559 | 610 | ||
560 | /* prepare things before vfork */ | 611 | /* prepare things before vfork */ |
561 | pas = getpwnam(user); | 612 | pas = getpwnam(user); |
562 | if (!pas) { | 613 | if (!pas) { |
563 | crondlog(WARN9 "can't get uid for %s", user); | 614 | bb_error_msg("can't get uid for %s", user); |
564 | goto err; | 615 | goto err; |
565 | } | 616 | } |
566 | set_env_vars(pas); | ||
567 | 617 | ||
618 | shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL; | ||
619 | prog = run_sendmail ? SENDMAIL : shell; | ||
620 | |||
621 | set_env_vars(pas, shell); | ||
622 | |||
623 | sv_logmode = logmode; | ||
568 | pid = vfork(); | 624 | pid = vfork(); |
569 | if (pid == 0) { | 625 | if (pid == 0) { |
570 | /* CHILD */ | 626 | /* CHILD */ |
571 | /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ | 627 | /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */ |
572 | change_user(pas); | 628 | change_user(pas); |
573 | if (DebugOpt) { | 629 | log5("child running %s", prog); |
574 | crondlog(LVL5 "child running %s", prog); | ||
575 | } | ||
576 | if (mailFd >= 0) { | 630 | if (mailFd >= 0) { |
577 | xmove_fd(mailFd, shell_cmd ? 1 : 0); | 631 | xmove_fd(mailFd, run_sendmail ? 0 : 1); |
578 | dup2(1, 2); | 632 | dup2(1, 2); |
579 | } | 633 | } |
580 | /* crond 3.0pl1-100 puts tasks in separate process groups */ | 634 | /* crond 3.0pl1-100 puts tasks in separate process groups */ |
581 | bb_setpgrp(); | 635 | bb_setpgrp(); |
582 | execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL); | 636 | if (!run_sendmail) |
583 | crondlog(ERR20 "can't execute '%s' for user %s", prog, user); | 637 | execlp(prog, prog, "-c", line->cl_cmd, (char *) NULL); |
584 | if (shell_cmd) { | 638 | else |
585 | fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd); | 639 | execlp(prog, prog, SENDMAIL_ARGS, (char *) NULL); |
586 | } | 640 | /* |
587 | _exit(EXIT_SUCCESS); | 641 | * I want this error message on stderr too, |
642 | * even if other messages go only to syslog: | ||
643 | */ | ||
644 | logmode |= LOGMODE_STDIO; | ||
645 | bb_error_msg_and_die("can't execute '%s' for user %s", prog, user); | ||
588 | } | 646 | } |
647 | logmode = sv_logmode; | ||
589 | 648 | ||
590 | if (pid < 0) { | 649 | if (pid < 0) { |
591 | /* FORK FAILED */ | 650 | bb_perror_msg("vfork"); |
592 | crondlog(ERR20 "can't vfork"); | ||
593 | err: | 651 | err: |
594 | pid = 0; | 652 | pid = 0; |
595 | } /* else: PARENT, FORK SUCCESS */ | 653 | } /* else: PARENT, FORK SUCCESS */ |
@@ -614,7 +672,7 @@ static void start_one_job(const char *user, CronLine *line) | |||
614 | 672 | ||
615 | if (line->cl_mailto) { | 673 | if (line->cl_mailto) { |
616 | /* Open mail file (owner is root so nobody can screw with it) */ | 674 | /* Open mail file (owner is root so nobody can screw with it) */ |
617 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); | 675 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, getpid()); |
618 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); | 676 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); |
619 | 677 | ||
620 | if (mailFd >= 0) { | 678 | if (mailFd >= 0) { |
@@ -622,18 +680,18 @@ static void start_one_job(const char *user, CronLine *line) | |||
622 | line->cl_cmd); | 680 | line->cl_cmd); |
623 | line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR); | 681 | line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR); |
624 | } else { | 682 | } else { |
625 | crondlog(ERR20 "can't create mail file %s for user %s, " | 683 | bb_error_msg("can't create mail file %s for user %s, " |
626 | "discarding output", mailFile, user); | 684 | "discarding output", mailFile, user); |
627 | } | 685 | } |
628 | } | 686 | } |
629 | 687 | ||
630 | line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd); | 688 | line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 0); |
631 | if (mailFd >= 0) { | 689 | if (mailFd >= 0) { |
632 | if (line->cl_pid <= 0) { | 690 | if (line->cl_pid <= 0) { |
633 | unlink(mailFile); | 691 | unlink(mailFile); |
634 | } else { | 692 | } else { |
635 | /* rename mail-file based on pid of process */ | 693 | /* rename mail-file based on pid of process */ |
636 | char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid); | 694 | char *mailFile2 = xasprintf("%s/cron.%s.%d", CRON_DIR, user, (int)line->cl_pid); |
637 | rename(mailFile, mailFile2); // TODO: xrename? | 695 | rename(mailFile, mailFile2); // TODO: xrename? |
638 | free(mailFile2); | 696 | free(mailFile2); |
639 | } | 697 | } |
@@ -665,7 +723,7 @@ static void process_finished_job(const char *user, CronLine *line) | |||
665 | * End of primary job - check for mail file. | 723 | * End of primary job - check for mail file. |
666 | * If size has changed and the file is still valid, we send it. | 724 | * If size has changed and the file is still valid, we send it. |
667 | */ | 725 | */ |
668 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid); | 726 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, (int)pid); |
669 | mailFd = open(mailFile, O_RDONLY); | 727 | mailFd = open(mailFile, O_RDONLY); |
670 | unlink(mailFile); | 728 | unlink(mailFile); |
671 | if (mailFd < 0) { | 729 | if (mailFd < 0) { |
@@ -683,43 +741,41 @@ static void process_finished_job(const char *user, CronLine *line) | |||
683 | } | 741 | } |
684 | line->cl_empty_mail_size = 0; | 742 | line->cl_empty_mail_size = 0; |
685 | /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */ | 743 | /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */ |
686 | line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL); | 744 | line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 1); |
687 | } | 745 | } |
688 | 746 | ||
689 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | 747 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |
690 | 748 | ||
691 | static void start_one_job(const char *user, CronLine *line) | 749 | static void start_one_job(const char *user, CronLine *line) |
692 | { | 750 | { |
751 | const char *shell; | ||
693 | struct passwd *pas; | 752 | struct passwd *pas; |
694 | pid_t pid; | 753 | pid_t pid; |
695 | 754 | ||
696 | pas = getpwnam(user); | 755 | pas = getpwnam(user); |
697 | if (!pas) { | 756 | if (!pas) { |
698 | crondlog(WARN9 "can't get uid for %s", user); | 757 | bb_error_msg("can't get uid for %s", user); |
699 | goto err; | 758 | goto err; |
700 | } | 759 | } |
701 | 760 | ||
702 | /* Prepare things before vfork */ | 761 | /* Prepare things before vfork */ |
703 | set_env_vars(pas); | 762 | shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL; |
763 | set_env_vars(pas, shell); | ||
704 | 764 | ||
705 | /* Fork as the user in question and run program */ | 765 | /* Fork as the user in question and run program */ |
706 | pid = vfork(); | 766 | pid = vfork(); |
707 | if (pid == 0) { | 767 | if (pid == 0) { |
708 | /* CHILD */ | 768 | /* CHILD */ |
709 | /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ | 769 | /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */ |
710 | change_user(pas); | 770 | change_user(pas); |
711 | if (DebugOpt) { | 771 | log5("child running %s", shell); |
712 | crondlog(LVL5 "child running %s", DEFAULT_SHELL); | ||
713 | } | ||
714 | /* crond 3.0pl1-100 puts tasks in separate process groups */ | 772 | /* crond 3.0pl1-100 puts tasks in separate process groups */ |
715 | bb_setpgrp(); | 773 | bb_setpgrp(); |
716 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL); | 774 | execl(shell, shell, "-c", line->cl_cmd, (char *) NULL); |
717 | crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user); | 775 | bb_error_msg_and_die("can't execute '%s' for user %s", shell, user); |
718 | _exit(EXIT_SUCCESS); | ||
719 | } | 776 | } |
720 | if (pid < 0) { | 777 | if (pid < 0) { |
721 | /* FORK FAILED */ | 778 | bb_perror_msg("vfork"); |
722 | crondlog(ERR20 "can't vfork"); | ||
723 | err: | 779 | err: |
724 | pid = 0; | 780 | pid = 0; |
725 | } | 781 | } |
@@ -751,24 +807,20 @@ static void flag_starting_jobs(time_t t1, time_t t2) | |||
751 | 807 | ||
752 | ptm = localtime(&t); | 808 | ptm = localtime(&t); |
753 | for (file = G.cron_files; file; file = file->cf_next) { | 809 | for (file = G.cron_files; file; file = file->cf_next) { |
754 | if (DebugOpt) | 810 | log5("file %s:", file->cf_username); |
755 | crondlog(LVL5 "file %s:", file->cf_username); | ||
756 | if (file->cf_deleted) | 811 | if (file->cf_deleted) |
757 | continue; | 812 | continue; |
758 | for (line = file->cf_lines; line; line = line->cl_next) { | 813 | for (line = file->cf_lines; line; line = line->cl_next) { |
759 | if (DebugOpt) | 814 | log5(" line %s", line->cl_cmd); |
760 | crondlog(LVL5 " line %s", line->cl_cmd); | ||
761 | if (line->cl_Mins[ptm->tm_min] | 815 | if (line->cl_Mins[ptm->tm_min] |
762 | && line->cl_Hrs[ptm->tm_hour] | 816 | && line->cl_Hrs[ptm->tm_hour] |
763 | && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) | 817 | && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) |
764 | && line->cl_Mons[ptm->tm_mon] | 818 | && line->cl_Mons[ptm->tm_mon] |
765 | ) { | 819 | ) { |
766 | if (DebugOpt) { | 820 | log5(" job: %d %s", |
767 | crondlog(LVL5 " job: %d %s", | ||
768 | (int)line->cl_pid, line->cl_cmd); | 821 | (int)line->cl_pid, line->cl_cmd); |
769 | } | ||
770 | if (line->cl_pid > 0) { | 822 | if (line->cl_pid > 0) { |
771 | crondlog(LVL8 "user %s: process already running: %s", | 823 | log8("user %s: process already running: %s", |
772 | file->cf_username, line->cl_cmd); | 824 | file->cf_username, line->cl_cmd); |
773 | } else if (line->cl_pid == 0) { | 825 | } else if (line->cl_pid == 0) { |
774 | line->cl_pid = -1; | 826 | line->cl_pid = -1; |
@@ -797,7 +849,7 @@ static void start_jobs(void) | |||
797 | 849 | ||
798 | start_one_job(file->cf_username, line); | 850 | start_one_job(file->cf_username, line); |
799 | pid = line->cl_pid; | 851 | pid = line->cl_pid; |
800 | crondlog(LVL8 "USER %s pid %3d cmd %s", | 852 | log8("USER %s pid %3d cmd %s", |
801 | file->cf_username, (int)pid, line->cl_cmd); | 853 | file->cf_username, (int)pid, line->cl_cmd); |
802 | if (pid < 0) { | 854 | if (pid < 0) { |
803 | file->cf_wants_starting = 1; | 855 | file->cf_wants_starting = 1; |
@@ -849,12 +901,21 @@ static int check_completions(void) | |||
849 | return num_still_running; | 901 | return num_still_running; |
850 | } | 902 | } |
851 | 903 | ||
904 | static void reopen_logfile_to_stderr(void) | ||
905 | { | ||
906 | if (G.log_filename) { | ||
907 | int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND); | ||
908 | if (logfd >= 0) | ||
909 | xmove_fd(logfd, STDERR_FILENO); | ||
910 | } | ||
911 | } | ||
912 | |||
852 | int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 913 | int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
853 | int crond_main(int argc UNUSED_PARAM, char **argv) | 914 | int crond_main(int argc UNUSED_PARAM, char **argv) |
854 | { | 915 | { |
855 | time_t t2; | 916 | time_t t2; |
856 | int rescan; | 917 | unsigned rescan; |
857 | int sleep_time; | 918 | unsigned sleep_time; |
858 | unsigned opts; | 919 | unsigned opts; |
859 | 920 | ||
860 | INIT_G(); | 921 | INIT_G(); |
@@ -880,10 +941,11 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
880 | logmode = LOGMODE_SYSLOG; | 941 | logmode = LOGMODE_SYSLOG; |
881 | } | 942 | } |
882 | 943 | ||
883 | xchdir(G.crontab_dir_name); | ||
884 | //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ | 944 | //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ |
885 | xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ | 945 | |
886 | crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level); | 946 | reopen_logfile_to_stderr(); |
947 | xchdir(G.crontab_dir_name); | ||
948 | log8("crond (busybox "BB_VER") started, log level %d", G.log_level); | ||
887 | rescan_crontab_dir(); | 949 | rescan_crontab_dir(); |
888 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); | 950 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); |
889 | 951 | ||
@@ -896,14 +958,14 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
896 | time_t t1; | 958 | time_t t1; |
897 | long dt; | 959 | long dt; |
898 | 960 | ||
899 | t1 = t2; | ||
900 | |||
901 | /* Synchronize to 1 minute, minimum 1 second */ | 961 | /* Synchronize to 1 minute, minimum 1 second */ |
902 | sleep(sleep_time - (time(NULL) % sleep_time) + 1); | 962 | t1 = t2; |
903 | 963 | sleep(sleep_time - (time(NULL) % sleep_time)); | |
904 | t2 = time(NULL); | 964 | t2 = time(NULL); |
905 | dt = (long)t2 - (long)t1; | 965 | dt = (long)t2 - (long)t1; |
906 | 966 | ||
967 | reopen_logfile_to_stderr(); | ||
968 | |||
907 | /* | 969 | /* |
908 | * The file 'cron.update' is checked to determine new cron | 970 | * The file 'cron.update' is checked to determine new cron |
909 | * jobs. The directory is rescanned once an hour to deal | 971 | * jobs. The directory is rescanned once an hour to deal |
@@ -931,20 +993,18 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
931 | rescan_crontab_dir(); | 993 | rescan_crontab_dir(); |
932 | } | 994 | } |
933 | process_cron_update_file(); | 995 | process_cron_update_file(); |
934 | if (DebugOpt) | 996 | log5("wakeup dt=%ld", dt); |
935 | crondlog(LVL5 "wakeup dt=%ld", dt); | ||
936 | if (dt < -60 * 60 || dt > 60 * 60) { | 997 | if (dt < -60 * 60 || dt > 60 * 60) { |
937 | crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60); | 998 | bb_error_msg("time disparity of %ld minutes detected", dt / 60); |
938 | /* and we do not run any jobs in this case */ | 999 | /* and we do not run any jobs in this case */ |
939 | } else if (dt > 0) { | 1000 | } else if (dt > 0) { |
940 | /* Usual case: time advances forward, as expected */ | 1001 | /* Usual case: time advances forward, as expected */ |
941 | flag_starting_jobs(t1, t2); | 1002 | flag_starting_jobs(t1, t2); |
942 | start_jobs(); | 1003 | start_jobs(); |
1004 | sleep_time = 60; | ||
943 | if (check_completions() > 0) { | 1005 | if (check_completions() > 0) { |
944 | /* some jobs are still running */ | 1006 | /* some jobs are still running */ |
945 | sleep_time = 10; | 1007 | sleep_time = 10; |
946 | } else { | ||
947 | sleep_time = 60; | ||
948 | } | 1008 | } |
949 | } | 1009 | } |
950 | /* else: time jumped back, do not run any jobs */ | 1010 | /* else: time jumped back, do not run any jobs */ |
diff --git a/miscutils/less.c b/miscutils/less.c index 574f222e0..d84df469c 100644 --- a/miscutils/less.c +++ b/miscutils/less.c | |||
@@ -404,6 +404,9 @@ static void fill_match_lines(unsigned pos); | |||
404 | * last_line_pos - screen line position of next char to be read | 404 | * last_line_pos - screen line position of next char to be read |
405 | * (takes into account tabs and backspaces) | 405 | * (takes into account tabs and backspaces) |
406 | * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error | 406 | * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error |
407 | * | ||
408 | * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs, | ||
409 | * "/search on very long input" and "reaching max line count" corner cases. | ||
407 | */ | 410 | */ |
408 | static void read_lines(void) | 411 | static void read_lines(void) |
409 | { | 412 | { |
@@ -414,9 +417,13 @@ static void read_lines(void) | |||
414 | #if ENABLE_FEATURE_LESS_REGEXP | 417 | #if ENABLE_FEATURE_LESS_REGEXP |
415 | unsigned old_max_fline = max_fline; | 418 | unsigned old_max_fline = max_fline; |
416 | time_t last_time = 0; | 419 | time_t last_time = 0; |
417 | unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */ | 420 | int had_progress = 2; |
418 | #endif | 421 | #endif |
419 | 422 | ||
423 | /* (careful: max_fline can be -1) */ | ||
424 | if (max_fline + 1 > MAXLINES) | ||
425 | return; | ||
426 | |||
420 | if (option_mask32 & FLAG_N) | 427 | if (option_mask32 & FLAG_N) |
421 | w -= 8; | 428 | w -= 8; |
422 | 429 | ||
@@ -441,6 +448,7 @@ static void read_lines(void) | |||
441 | char c; | 448 | char c; |
442 | /* if no unprocessed chars left, eat more */ | 449 | /* if no unprocessed chars left, eat more */ |
443 | if (readpos >= readeof) { | 450 | if (readpos >= readeof) { |
451 | errno = 0; | ||
444 | ndelay_on(0); | 452 | ndelay_on(0); |
445 | eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); | 453 | eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); |
446 | ndelay_off(0); | 454 | ndelay_off(0); |
@@ -448,6 +456,7 @@ static void read_lines(void) | |||
448 | readeof = eof_error; | 456 | readeof = eof_error; |
449 | if (eof_error <= 0) | 457 | if (eof_error <= 0) |
450 | goto reached_eof; | 458 | goto reached_eof; |
459 | IF_FEATURE_LESS_REGEXP(had_progress = 1;) | ||
451 | } | 460 | } |
452 | c = readbuf[readpos]; | 461 | c = readbuf[readpos]; |
453 | /* backspace? [needed for manpages] */ | 462 | /* backspace? [needed for manpages] */ |
@@ -519,31 +528,23 @@ static void read_lines(void) | |||
519 | #endif | 528 | #endif |
520 | } | 529 | } |
521 | if (eof_error <= 0) { | 530 | if (eof_error <= 0) { |
522 | if (eof_error < 0) { | ||
523 | if (errno == EAGAIN) { | ||
524 | /* not yet eof or error, reset flag (or else | ||
525 | * we will hog CPU - select() will return | ||
526 | * immediately */ | ||
527 | eof_error = 1; | ||
528 | } else { | ||
529 | print_statusline(bb_msg_read_error); | ||
530 | } | ||
531 | } | ||
532 | #if !ENABLE_FEATURE_LESS_REGEXP | 531 | #if !ENABLE_FEATURE_LESS_REGEXP |
533 | break; | 532 | break; |
534 | #else | 533 | #else |
535 | if (wanted_match < num_matches) { | 534 | if (wanted_match < num_matches) { |
536 | break; | 535 | break; |
537 | } else { /* goto_match called us */ | 536 | } /* else: goto_match() called us */ |
537 | if (errno == EAGAIN) { | ||
538 | time_t t = time(NULL); | 538 | time_t t = time(NULL); |
539 | if (t != last_time) { | 539 | if (t != last_time) { |
540 | last_time = t; | 540 | last_time = t; |
541 | if (--seconds_p1 == 0) | 541 | if (--had_progress < 0) |
542 | break; | 542 | break; |
543 | } | 543 | } |
544 | sched_yield(); | 544 | sched_yield(); |
545 | goto again0; /* go loop again (max 2 seconds) */ | 545 | goto again0; |
546 | } | 546 | } |
547 | break; | ||
547 | #endif | 548 | #endif |
548 | } | 549 | } |
549 | max_fline++; | 550 | max_fline++; |
@@ -551,6 +552,15 @@ static void read_lines(void) | |||
551 | p = current_line; | 552 | p = current_line; |
552 | last_line_pos = 0; | 553 | last_line_pos = 0; |
553 | } /* end of "read lines until we reach cur_fline" loop */ | 554 | } /* end of "read lines until we reach cur_fline" loop */ |
555 | |||
556 | if (eof_error < 0) { | ||
557 | if (errno == EAGAIN) { | ||
558 | eof_error = 1; | ||
559 | } else { | ||
560 | print_statusline(bb_msg_read_error); | ||
561 | } | ||
562 | } | ||
563 | |||
554 | fill_match_lines(old_max_fline); | 564 | fill_match_lines(old_max_fline); |
555 | #if ENABLE_FEATURE_LESS_REGEXP | 565 | #if ENABLE_FEATURE_LESS_REGEXP |
556 | /* prevent us from being stuck in search for a match */ | 566 | /* prevent us from being stuck in search for a match */ |
diff --git a/modutils/insmod.c b/modutils/insmod.c index 887d9f2a3..9c3c992a5 100644 --- a/modutils/insmod.c +++ b/modutils/insmod.c | |||
@@ -21,7 +21,7 @@ | |||
21 | //usage: IF_NOT_FEATURE_2_4_MODULES("FILE ") | 21 | //usage: IF_NOT_FEATURE_2_4_MODULES("FILE ") |
22 | //usage: "[SYMBOL=VALUE]..." | 22 | //usage: "[SYMBOL=VALUE]..." |
23 | //usage:#define insmod_full_usage "\n\n" | 23 | //usage:#define insmod_full_usage "\n\n" |
24 | //usage: "Load the specified kernel modules into the kernel" | 24 | //usage: "Load kernel module" |
25 | //usage: IF_FEATURE_2_4_MODULES( "\n" | 25 | //usage: IF_FEATURE_2_4_MODULES( "\n" |
26 | //usage: "\n -f Force module to load into the wrong kernel version" | 26 | //usage: "\n -f Force module to load into the wrong kernel version" |
27 | //usage: "\n -k Make module autoclean-able" | 27 | //usage: "\n -k Make module autoclean-able" |
diff --git a/modutils/modprobe-small.c b/modutils/modprobe-small.c index 5b7836344..91e0c1380 100644 --- a/modutils/modprobe-small.c +++ b/modutils/modprobe-small.c | |||
@@ -9,10 +9,10 @@ | |||
9 | */ | 9 | */ |
10 | 10 | ||
11 | //applet:IF_MODPROBE_SMALL(APPLET(modprobe, BB_DIR_SBIN, BB_SUID_DROP)) | 11 | //applet:IF_MODPROBE_SMALL(APPLET(modprobe, BB_DIR_SBIN, BB_SUID_DROP)) |
12 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(depmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)) | 12 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(depmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, depmod)) |
13 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(insmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)) | 13 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(insmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, insmod)) |
14 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(lsmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)) | 14 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(lsmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, lsmod)) |
15 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(rmmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)) | 15 | //applet:IF_MODPROBE_SMALL(APPLET_ODDNAME(rmmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, rmmod)) |
16 | 16 | ||
17 | #include "libbb.h" | 17 | #include "libbb.h" |
18 | /* After libbb.h, since it needs sys/types.h on some systems */ | 18 | /* After libbb.h, since it needs sys/types.h on some systems */ |
@@ -22,6 +22,9 @@ | |||
22 | extern int init_module(void *module, unsigned long len, const char *options); | 22 | extern int init_module(void *module, unsigned long len, const char *options); |
23 | extern int delete_module(const char *module, unsigned flags); | 23 | extern int delete_module(const char *module, unsigned flags); |
24 | extern int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret); | 24 | extern int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret); |
25 | /* linux/include/linux/module.h has limit of 64 chars on module names */ | ||
26 | #undef MODULE_NAME_LEN | ||
27 | #define MODULE_NAME_LEN 64 | ||
25 | 28 | ||
26 | 29 | ||
27 | #if 1 | 30 | #if 1 |
@@ -143,6 +146,19 @@ static void replace(char *s, char what, char with) | |||
143 | } | 146 | } |
144 | } | 147 | } |
145 | 148 | ||
149 | static char *filename2modname(const char *filename, char *modname) | ||
150 | { | ||
151 | int i; | ||
152 | char *from; | ||
153 | |||
154 | from = bb_get_last_path_component_nostrip(filename); | ||
155 | for (i = 0; i < (MODULE_NAME_LEN-1) && from[i] != '\0' && from[i] != '.'; i++) | ||
156 | modname[i] = (from[i] == '-') ? '_' : from[i]; | ||
157 | modname[i] = '\0'; | ||
158 | |||
159 | return modname; | ||
160 | } | ||
161 | |||
146 | /* Take "word word", return malloced "word",NUL,"word",NUL,NUL */ | 162 | /* Take "word word", return malloced "word",NUL,"word",NUL,NUL */ |
147 | static char* str_2_list(const char *str) | 163 | static char* str_2_list(const char *str) |
148 | { | 164 | { |
@@ -212,6 +228,7 @@ static void parse_module(module_info *info, const char *pathname) | |||
212 | reset_stringbuf(); | 228 | reset_stringbuf(); |
213 | pos = 0; | 229 | pos = 0; |
214 | while (1) { | 230 | while (1) { |
231 | unsigned start = stringbuf_idx; | ||
215 | ptr = find_keyword(module_image + pos, len - pos, "alias="); | 232 | ptr = find_keyword(module_image + pos, len - pos, "alias="); |
216 | if (!ptr) { | 233 | if (!ptr) { |
217 | ptr = find_keyword(module_image + pos, len - pos, "__ksymtab_"); | 234 | ptr = find_keyword(module_image + pos, len - pos, "__ksymtab_"); |
@@ -228,6 +245,31 @@ static void parse_module(module_info *info, const char *pathname) | |||
228 | } | 245 | } |
229 | append(ptr); | 246 | append(ptr); |
230 | appendc(' '); | 247 | appendc(' '); |
248 | /* | ||
249 | * Don't add redundant aliases, such as: | ||
250 | * libcrc32c.ko symbol:crc32c symbol:crc32c | ||
251 | */ | ||
252 | if (start) { /* "if we aren't the first alias" */ | ||
253 | char *found, *last; | ||
254 | stringbuf[stringbuf_idx] = '\0'; | ||
255 | last = stringbuf + start; | ||
256 | /* | ||
257 | * String at last-1 is " symbol:crc32c " | ||
258 | * (with both leading and trailing spaces). | ||
259 | */ | ||
260 | if (strncmp(stringbuf, last, stringbuf_idx - start) == 0) | ||
261 | /* First alias matches us */ | ||
262 | found = stringbuf; | ||
263 | else | ||
264 | /* Does any other alias match? */ | ||
265 | found = strstr(stringbuf, last-1); | ||
266 | if (found < last-1) { | ||
267 | /* There is absolutely the same string before us */ | ||
268 | dbg2_error_msg("redundant:'%s'", last); | ||
269 | stringbuf_idx = start; | ||
270 | goto skip; | ||
271 | } | ||
272 | } | ||
231 | skip: | 273 | skip: |
232 | pos = (ptr - module_image); | 274 | pos = (ptr - module_image); |
233 | } | 275 | } |
@@ -251,14 +293,13 @@ static void parse_module(module_info *info, const char *pathname) | |||
251 | 293 | ||
252 | static int pathname_matches_modname(const char *pathname, const char *modname) | 294 | static int pathname_matches_modname(const char *pathname, const char *modname) |
253 | { | 295 | { |
296 | int r; | ||
297 | char name[MODULE_NAME_LEN]; | ||
254 | const char *fname = bb_get_last_path_component_nostrip(pathname); | 298 | const char *fname = bb_get_last_path_component_nostrip(pathname); |
255 | const char *suffix = strrstr(fname, ".ko"); | 299 | const char *suffix = strrstr(fname, ".ko"); |
256 | //TODO: can do without malloc? | 300 | safe_strncpy(name, fname, suffix - fname); |
257 | char *name = xstrndup(fname, suffix - fname); | ||
258 | int r; | ||
259 | replace(name, '-', '_'); | 301 | replace(name, '-', '_'); |
260 | r = (strcmp(name, modname) == 0); | 302 | r = (strcmp(name, modname) == 0); |
261 | free(name); | ||
262 | return r; | 303 | return r; |
263 | } | 304 | } |
264 | 305 | ||
@@ -421,11 +462,12 @@ static void write_out_dep_bb(int fd) | |||
421 | } | 462 | } |
422 | } | 463 | } |
423 | 464 | ||
424 | static module_info* find_alias(const char *alias) | 465 | static module_info** find_alias(const char *alias) |
425 | { | 466 | { |
426 | int i; | 467 | int i; |
427 | int dep_bb_fd; | 468 | int dep_bb_fd; |
428 | module_info *result; | 469 | int infoidx; |
470 | module_info **infovec; | ||
429 | dbg1_error_msg("find_alias('%s')", alias); | 471 | dbg1_error_msg("find_alias('%s')", alias); |
430 | 472 | ||
431 | try_again: | 473 | try_again: |
@@ -438,7 +480,9 @@ static module_info* find_alias(const char *alias) | |||
438 | if (!modinfo[i].aliases) { | 480 | if (!modinfo[i].aliases) { |
439 | parse_module(&modinfo[i], modinfo[i].pathname); | 481 | parse_module(&modinfo[i], modinfo[i].pathname); |
440 | } | 482 | } |
441 | return &modinfo[i]; | 483 | infovec = xzalloc(2 * sizeof(infovec[0])); |
484 | infovec[0] = &modinfo[i]; | ||
485 | return infovec; | ||
442 | } | 486 | } |
443 | i++; | 487 | i++; |
444 | } | 488 | } |
@@ -451,16 +495,13 @@ static module_info* find_alias(const char *alias) | |||
451 | 495 | ||
452 | /* Scan all module bodies, extract modinfo (it contains aliases) */ | 496 | /* Scan all module bodies, extract modinfo (it contains aliases) */ |
453 | i = 0; | 497 | i = 0; |
454 | result = NULL; | 498 | infoidx = 0; |
499 | infovec = NULL; | ||
455 | while (modinfo[i].pathname) { | 500 | while (modinfo[i].pathname) { |
456 | char *desc, *s; | 501 | char *desc, *s; |
457 | if (!modinfo[i].aliases) { | 502 | if (!modinfo[i].aliases) { |
458 | parse_module(&modinfo[i], modinfo[i].pathname); | 503 | parse_module(&modinfo[i], modinfo[i].pathname); |
459 | } | 504 | } |
460 | if (result) { | ||
461 | i++; | ||
462 | continue; | ||
463 | } | ||
464 | /* "alias1 symbol:sym1 alias2 symbol:sym2" */ | 505 | /* "alias1 symbol:sym1 alias2 symbol:sym2" */ |
465 | desc = str_2_list(modinfo[i].aliases); | 506 | desc = str_2_list(modinfo[i].aliases); |
466 | /* Does matching substring exist? */ | 507 | /* Does matching substring exist? */ |
@@ -472,13 +513,12 @@ static module_info* find_alias(const char *alias) | |||
472 | if (fnmatch(s, alias, 0) == 0) { | 513 | if (fnmatch(s, alias, 0) == 0) { |
473 | dbg1_error_msg("found alias '%s' in module '%s'", | 514 | dbg1_error_msg("found alias '%s' in module '%s'", |
474 | alias, modinfo[i].pathname); | 515 | alias, modinfo[i].pathname); |
475 | result = &modinfo[i]; | 516 | infovec = xrealloc_vector(infovec, 1, infoidx); |
517 | infovec[infoidx++] = &modinfo[i]; | ||
476 | break; | 518 | break; |
477 | } | 519 | } |
478 | } | 520 | } |
479 | free(desc); | 521 | free(desc); |
480 | if (result && dep_bb_fd < 0) | ||
481 | return result; | ||
482 | i++; | 522 | i++; |
483 | } | 523 | } |
484 | 524 | ||
@@ -487,8 +527,8 @@ static module_info* find_alias(const char *alias) | |||
487 | write_out_dep_bb(dep_bb_fd); | 527 | write_out_dep_bb(dep_bb_fd); |
488 | } | 528 | } |
489 | 529 | ||
490 | dbg1_error_msg("find_alias '%s' returns %p", alias, result); | 530 | dbg1_error_msg("find_alias '%s' returns %d results", alias, infoidx); |
491 | return result; | 531 | return infovec; |
492 | } | 532 | } |
493 | 533 | ||
494 | #if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED | 534 | #if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED |
@@ -524,14 +564,23 @@ static int already_loaded(const char *name) | |||
524 | static void process_module(char *name, const char *cmdline_options) | 564 | static void process_module(char *name, const char *cmdline_options) |
525 | { | 565 | { |
526 | char *s, *deps, *options; | 566 | char *s, *deps, *options; |
567 | module_info **infovec; | ||
527 | module_info *info; | 568 | module_info *info; |
569 | int infoidx; | ||
528 | int is_rmmod = (option_mask32 & OPT_r) != 0; | 570 | int is_rmmod = (option_mask32 & OPT_r) != 0; |
571 | |||
529 | dbg1_error_msg("process_module('%s','%s')", name, cmdline_options); | 572 | dbg1_error_msg("process_module('%s','%s')", name, cmdline_options); |
530 | 573 | ||
531 | replace(name, '-', '_'); | 574 | replace(name, '-', '_'); |
532 | 575 | ||
533 | dbg1_error_msg("already_loaded:%d is_rmmod:%d", already_loaded(name), is_rmmod); | 576 | dbg1_error_msg("already_loaded:%d is_rmmod:%d", already_loaded(name), is_rmmod); |
534 | if (already_loaded(name) != is_rmmod) { | 577 | /* |
578 | * We used to have "is_rmmod != already_loaded(name)" check here, but | ||
579 | * modprobe -r pci:v00008086d00007010sv00000000sd00000000bc01sc01i80 | ||
580 | * won't unload modules (there are more than one) | ||
581 | * which have this alias. | ||
582 | */ | ||
583 | if (!is_rmmod && already_loaded(name)) { | ||
535 | dbg1_error_msg("nothing to do for '%s'", name); | 584 | dbg1_error_msg("nothing to do for '%s'", name); |
536 | return; | 585 | return; |
537 | } | 586 | } |
@@ -560,39 +609,51 @@ static void process_module(char *name, const char *cmdline_options) | |||
560 | if (!module_count) { | 609 | if (!module_count) { |
561 | /* Scan module directory. This is done only once. | 610 | /* Scan module directory. This is done only once. |
562 | * It will attempt module load, and will exit(EXIT_SUCCESS) | 611 | * It will attempt module load, and will exit(EXIT_SUCCESS) |
563 | * on success. */ | 612 | * on success. |
613 | */ | ||
564 | module_found_idx = -1; | 614 | module_found_idx = -1; |
565 | recursive_action(".", | 615 | recursive_action(".", |
566 | ACTION_RECURSE, /* flags */ | 616 | ACTION_RECURSE, /* flags */ |
567 | fileAction, /* file action */ | 617 | fileAction, /* file action */ |
568 | NULL, /* dir action */ | 618 | NULL, /* dir action */ |
569 | name, /* user data */ | 619 | name, /* user data */ |
570 | 0); /* depth */ | 620 | 0 /* depth */ |
621 | ); | ||
571 | dbg1_error_msg("dirscan complete"); | 622 | dbg1_error_msg("dirscan complete"); |
572 | /* Module was not found, or load failed, or is_rmmod */ | 623 | /* Module was not found, or load failed, or is_rmmod */ |
573 | if (module_found_idx >= 0) { /* module was found */ | 624 | if (module_found_idx >= 0) { /* module was found */ |
574 | info = &modinfo[module_found_idx]; | 625 | infovec = xzalloc(2 * sizeof(infovec[0])); |
626 | infovec[0] = &modinfo[module_found_idx]; | ||
575 | } else { /* search for alias, not a plain module name */ | 627 | } else { /* search for alias, not a plain module name */ |
576 | info = find_alias(name); | 628 | infovec = find_alias(name); |
577 | } | 629 | } |
578 | } else { | 630 | } else { |
579 | info = find_alias(name); | 631 | infovec = find_alias(name); |
580 | } | 632 | } |
581 | 633 | ||
582 | // Problem here: there can be more than one module | 634 | /* There can be more than one module for the given alias. For example, |
583 | // for the given alias. For example, | 635 | * "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches |
584 | // "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches | 636 | * ata_piix because it has alias "pci:v00008086d00007010sv*sd*bc*sc*i*" |
585 | // ata_piix because it has an alias "pci:v00008086d00007010sv*sd*bc*sc*i*" | 637 | * and ata_generic, it has alias "pci:v*d*sv*sd*bc01sc01i*" |
586 | // and ata_generic, it has an alias "alias=pci:v*d*sv*sd*bc01sc01i*" | 638 | * Standard modprobe loads them both. We achieve it by returning |
587 | // Standard modprobe would load them both. | 639 | * a *list* of modinfo pointers from find_alias(). |
588 | // In this code, find_alias() returns only the first matching module. | 640 | */ |
589 | 641 | ||
590 | /* rmmod? unload it by name */ | 642 | /* rmmod or modprobe -r? unload module(s) */ |
591 | if (is_rmmod) { | 643 | if (is_rmmod) { |
592 | if (delete_module(name, O_NONBLOCK | O_EXCL) != 0) { | 644 | infoidx = 0; |
593 | if (!(option_mask32 & OPT_q)) | 645 | while ((info = infovec[infoidx++]) != NULL) { |
594 | bb_perror_msg("remove '%s'", name); | 646 | int r; |
595 | goto ret; | 647 | char modname[MODULE_NAME_LEN]; |
648 | |||
649 | filename2modname(info->pathname, modname); | ||
650 | r = delete_module(modname, O_NONBLOCK | O_EXCL); | ||
651 | dbg1_error_msg("delete_module('%s', O_NONBLOCK | O_EXCL):%d", modname, r); | ||
652 | if (r != 0) { | ||
653 | if (!(option_mask32 & OPT_q)) | ||
654 | bb_perror_msg("remove '%s'", modname); | ||
655 | goto ret; | ||
656 | } | ||
596 | } | 657 | } |
597 | 658 | ||
598 | if (applet_name[0] == 'r') { | 659 | if (applet_name[0] == 'r') { |
@@ -608,7 +669,7 @@ static void process_module(char *name, const char *cmdline_options) | |||
608 | */ | 669 | */ |
609 | } | 670 | } |
610 | 671 | ||
611 | if (!info) { | 672 | if (!infovec) { |
612 | /* both dirscan and find_alias found nothing */ | 673 | /* both dirscan and find_alias found nothing */ |
613 | if (!is_rmmod && applet_name[0] != 'd') /* it wasn't rmmod or depmod */ | 674 | if (!is_rmmod && applet_name[0] != 'd') /* it wasn't rmmod or depmod */ |
614 | bb_error_msg("module '%s' not found", name); | 675 | bb_error_msg("module '%s' not found", name); |
@@ -616,36 +677,41 @@ static void process_module(char *name, const char *cmdline_options) | |||
616 | goto ret; | 677 | goto ret; |
617 | } | 678 | } |
618 | 679 | ||
619 | /* Iterate thru dependencies, trying to (un)load them */ | 680 | infoidx = 0; |
620 | deps = str_2_list(info->deps); | 681 | while ((info = infovec[infoidx++]) != NULL) { |
621 | for (s = deps; *s; s += strlen(s) + 1) { | 682 | /* Iterate thru dependencies, trying to (un)load them */ |
622 | //if (strcmp(name, s) != 0) // N.B. do loops exist? | 683 | deps = str_2_list(info->deps); |
623 | dbg1_error_msg("recurse on dep '%s'", s); | 684 | for (s = deps; *s; s += strlen(s) + 1) { |
624 | process_module(s, NULL); | 685 | //if (strcmp(name, s) != 0) // N.B. do loops exist? |
625 | dbg1_error_msg("recurse on dep '%s' done", s); | 686 | dbg1_error_msg("recurse on dep '%s'", s); |
626 | } | 687 | process_module(s, NULL); |
627 | free(deps); | 688 | dbg1_error_msg("recurse on dep '%s' done", s); |
689 | } | ||
690 | free(deps); | ||
628 | 691 | ||
629 | /* modprobe -> load it */ | 692 | if (is_rmmod) |
630 | if (!is_rmmod) { | 693 | continue; |
631 | if (!options || strstr(options, "blacklist") == NULL) { | 694 | |
632 | errno = 0; | 695 | /* We are modprobe: load it */ |
633 | if (load_module(info->pathname, options) != 0) { | 696 | if (options && strstr(options, "blacklist")) { |
634 | if (EEXIST != errno) { | ||
635 | bb_error_msg("'%s': %s", | ||
636 | info->pathname, | ||
637 | moderror(errno)); | ||
638 | } else { | ||
639 | dbg1_error_msg("'%s': %s", | ||
640 | info->pathname, | ||
641 | moderror(errno)); | ||
642 | } | ||
643 | } | ||
644 | } else { | ||
645 | dbg1_error_msg("'%s': blacklisted", info->pathname); | 697 | dbg1_error_msg("'%s': blacklisted", info->pathname); |
698 | continue; | ||
699 | } | ||
700 | errno = 0; | ||
701 | if (load_module(info->pathname, options) != 0) { | ||
702 | if (EEXIST != errno) { | ||
703 | bb_error_msg("'%s': %s", | ||
704 | info->pathname, | ||
705 | moderror(errno)); | ||
706 | } else { | ||
707 | dbg1_error_msg("'%s': %s", | ||
708 | info->pathname, | ||
709 | moderror(errno)); | ||
710 | } | ||
646 | } | 711 | } |
647 | } | 712 | } |
648 | ret: | 713 | ret: |
714 | free(infovec); | ||
649 | free(options); | 715 | free(options); |
650 | //TODO: return load attempt result from process_module. | 716 | //TODO: return load attempt result from process_module. |
651 | //If dep didn't load ok, continuing makes little sense. | 717 | //If dep didn't load ok, continuing makes little sense. |
@@ -703,10 +769,6 @@ The following options are useful for people managing distributions: | |||
703 | 769 | ||
704 | //usage:#if ENABLE_MODPROBE_SMALL | 770 | //usage:#if ENABLE_MODPROBE_SMALL |
705 | 771 | ||
706 | //// Note: currently, help system shows modprobe --help text for all aliased cmds | ||
707 | //// (see APPLET_ODDNAME macro definition). | ||
708 | //// All other help texts defined below are not used. FIXME? | ||
709 | |||
710 | //usage:#define depmod_trivial_usage NOUSAGE_STR | 772 | //usage:#define depmod_trivial_usage NOUSAGE_STR |
711 | //usage:#define depmod_full_usage "" | 773 | //usage:#define depmod_full_usage "" |
712 | 774 | ||
@@ -720,7 +782,7 @@ The following options are useful for people managing distributions: | |||
720 | //usage: IF_NOT_FEATURE_2_4_MODULES("FILE ") | 782 | //usage: IF_NOT_FEATURE_2_4_MODULES("FILE ") |
721 | //usage: "[SYMBOL=VALUE]..." | 783 | //usage: "[SYMBOL=VALUE]..." |
722 | //usage:#define insmod_full_usage "\n\n" | 784 | //usage:#define insmod_full_usage "\n\n" |
723 | //usage: "Load the specified kernel modules into the kernel" | 785 | //usage: "Load kernel module" |
724 | //usage: IF_FEATURE_2_4_MODULES( "\n" | 786 | //usage: IF_FEATURE_2_4_MODULES( "\n" |
725 | //usage: "\n -f Force module to load into the wrong kernel version" | 787 | //usage: "\n -f Force module to load into the wrong kernel version" |
726 | //usage: "\n -k Make module autoclean-able" | 788 | //usage: "\n -k Make module autoclean-able" |
@@ -745,7 +807,7 @@ The following options are useful for people managing distributions: | |||
745 | //usage: "$ rmmod tulip\n" | 807 | //usage: "$ rmmod tulip\n" |
746 | 808 | ||
747 | //usage:#define modprobe_trivial_usage | 809 | //usage:#define modprobe_trivial_usage |
748 | //usage: "[-qfwrsv] MODULE [symbol=value]..." | 810 | //usage: "[-qfwrsv] MODULE [SYMBOL=VALUE]..." |
749 | //usage:#define modprobe_full_usage "\n\n" | 811 | //usage:#define modprobe_full_usage "\n\n" |
750 | //usage: " -r Remove MODULE (stacks) or do autoclean" | 812 | //usage: " -r Remove MODULE (stacks) or do autoclean" |
751 | //usage: "\n -q Quiet" | 813 | //usage: "\n -q Quiet" |
diff --git a/modutils/modprobe.c b/modutils/modprobe.c index 7f7446d8e..f08f0850d 100644 --- a/modutils/modprobe.c +++ b/modutils/modprobe.c | |||
@@ -87,7 +87,7 @@ | |||
87 | //usage: | 87 | //usage: |
88 | //usage:#define modprobe_trivial_usage | 88 | //usage:#define modprobe_trivial_usage |
89 | //usage: "[-alrqvsD" IF_FEATURE_MODPROBE_BLACKLIST("b") "]" | 89 | //usage: "[-alrqvsD" IF_FEATURE_MODPROBE_BLACKLIST("b") "]" |
90 | //usage: " MODULE [symbol=value]..." | 90 | //usage: " MODULE [SYMBOL=VALUE]..." |
91 | //usage:#define modprobe_full_usage "\n\n" | 91 | //usage:#define modprobe_full_usage "\n\n" |
92 | //usage: " -a Load multiple MODULEs" | 92 | //usage: " -a Load multiple MODULEs" |
93 | //usage: "\n -l List (MODULE is a pattern)" | 93 | //usage: "\n -l List (MODULE is a pattern)" |
diff --git a/networking/Config.src b/networking/Config.src index ca0ddcdd9..fbad7ecb2 100644 --- a/networking/Config.src +++ b/networking/Config.src | |||
@@ -664,6 +664,14 @@ config FEATURE_NTPD_SERVER | |||
664 | Make ntpd usable as a NTP server. If you disable this option | 664 | Make ntpd usable as a NTP server. If you disable this option |
665 | ntpd will be usable only as a NTP client. | 665 | ntpd will be usable only as a NTP client. |
666 | 666 | ||
667 | config FEATURE_NTPD_CONF | ||
668 | bool "Make ntpd understand /etc/ntp.conf" | ||
669 | default y | ||
670 | depends on NTPD | ||
671 | help | ||
672 | Make ntpd look in /etc/ntp.conf for peers. Only "server address" | ||
673 | is supported. | ||
674 | |||
667 | config PSCAN | 675 | config PSCAN |
668 | bool "pscan" | 676 | bool "pscan" |
669 | default y | 677 | default y |
diff --git a/networking/ifupdown.c b/networking/ifupdown.c index e1ea351a4..c35d97a1a 100644 --- a/networking/ifupdown.c +++ b/networking/ifupdown.c | |||
@@ -555,7 +555,7 @@ static int FAST_FUNC dhcp_up(struct interface_defn_t *ifd, execfn *exec) | |||
555 | return 0; | 555 | return 0; |
556 | # endif | 556 | # endif |
557 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { | 557 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { |
558 | if (exists_execable(ext_dhcp_clients[i].name)) | 558 | if (executable_exists(ext_dhcp_clients[i].name)) |
559 | return execute(ext_dhcp_clients[i].startcmd, ifd, exec); | 559 | return execute(ext_dhcp_clients[i].startcmd, ifd, exec); |
560 | } | 560 | } |
561 | bb_error_msg("no dhcp clients found"); | 561 | bb_error_msg("no dhcp clients found"); |
@@ -592,7 +592,7 @@ static int FAST_FUNC dhcp_down(struct interface_defn_t *ifd, execfn *exec) | |||
592 | unsigned i; | 592 | unsigned i; |
593 | 593 | ||
594 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { | 594 | for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { |
595 | if (exists_execable(ext_dhcp_clients[i].name)) { | 595 | if (executable_exists(ext_dhcp_clients[i].name)) { |
596 | result = execute(ext_dhcp_clients[i].stopcmd, ifd, exec); | 596 | result = execute(ext_dhcp_clients[i].stopcmd, ifd, exec); |
597 | if (result) | 597 | if (result) |
598 | break; | 598 | break; |
diff --git a/networking/ntpd.c b/networking/ntpd.c index 44592ce54..59607ed23 100644 --- a/networking/ntpd.c +++ b/networking/ntpd.c | |||
@@ -42,6 +42,13 @@ | |||
42 | //usage: ) | 42 | //usage: ) |
43 | //usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 mins" | 43 | //usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 mins" |
44 | //usage: "\n -p PEER Obtain time from PEER (may be repeated)" | 44 | //usage: "\n -p PEER Obtain time from PEER (may be repeated)" |
45 | //usage: IF_FEATURE_NTPD_CONF( | ||
46 | //usage: "\n If -p is not given, read /etc/ntp.conf" | ||
47 | //usage: ) | ||
48 | |||
49 | // -l and -p options are not compatible with "standard" ntpd: | ||
50 | // it has them as "-l logfile" and "-p pidfile". | ||
51 | // -S and -w are not compat either, "standard" ntpd has no such opts. | ||
45 | 52 | ||
46 | #include "libbb.h" | 53 | #include "libbb.h" |
47 | #include <math.h> | 54 | #include <math.h> |
@@ -245,6 +252,9 @@ typedef struct { | |||
245 | * or when receive times out (if p_fd >= 0): */ | 252 | * or when receive times out (if p_fd >= 0): */ |
246 | double next_action_time; | 253 | double next_action_time; |
247 | double p_xmttime; | 254 | double p_xmttime; |
255 | double p_raw_delay; | ||
256 | /* p_raw_delay is set even by "high delay" packets */ | ||
257 | /* lastpkt_delay isn't */ | ||
248 | double lastpkt_recv_time; | 258 | double lastpkt_recv_time; |
249 | double lastpkt_delay; | 259 | double lastpkt_delay; |
250 | double lastpkt_rootdelay; | 260 | double lastpkt_rootdelay; |
@@ -730,7 +740,7 @@ reset_peer_stats(peer_t *p, double offset) | |||
730 | } | 740 | } |
731 | 741 | ||
732 | static void | 742 | static void |
733 | add_peers(char *s) | 743 | add_peers(const char *s) |
734 | { | 744 | { |
735 | peer_t *p; | 745 | peer_t *p; |
736 | 746 | ||
@@ -1678,7 +1688,8 @@ recv_and_process_peer_pkt(peer_t *p) | |||
1678 | ssize_t size; | 1688 | ssize_t size; |
1679 | msg_t msg; | 1689 | msg_t msg; |
1680 | double T1, T2, T3, T4; | 1690 | double T1, T2, T3, T4; |
1681 | double dv, offset; | 1691 | double offset; |
1692 | double prev_delay, delay; | ||
1682 | unsigned interval; | 1693 | unsigned interval; |
1683 | datapoint_t *datapoint; | 1694 | datapoint_t *datapoint; |
1684 | peer_t *q; | 1695 | peer_t *q; |
@@ -1738,12 +1749,6 @@ recv_and_process_peer_pkt(peer_t *p) | |||
1738 | // if (msg.m_rootdelay / 2 + msg.m_rootdisp >= MAXDISP || p->lastpkt_reftime > msg.m_xmt) | 1749 | // if (msg.m_rootdelay / 2 + msg.m_rootdisp >= MAXDISP || p->lastpkt_reftime > msg.m_xmt) |
1739 | // return; /* invalid header values */ | 1750 | // return; /* invalid header values */ |
1740 | 1751 | ||
1741 | p->lastpkt_status = msg.m_status; | ||
1742 | p->lastpkt_stratum = msg.m_stratum; | ||
1743 | p->lastpkt_rootdelay = sfp_to_d(msg.m_rootdelay); | ||
1744 | p->lastpkt_rootdisp = sfp_to_d(msg.m_rootdisp); | ||
1745 | p->lastpkt_refid = msg.m_refid; | ||
1746 | |||
1747 | /* | 1752 | /* |
1748 | * From RFC 2030 (with a correction to the delay math): | 1753 | * From RFC 2030 (with a correction to the delay math): |
1749 | * | 1754 | * |
@@ -1763,28 +1768,35 @@ recv_and_process_peer_pkt(peer_t *p) | |||
1763 | T3 = lfp_to_d(msg.m_xmttime); | 1768 | T3 = lfp_to_d(msg.m_xmttime); |
1764 | T4 = G.cur_time; | 1769 | T4 = G.cur_time; |
1765 | 1770 | ||
1766 | p->lastpkt_recv_time = T4; | ||
1767 | VERB6 bb_error_msg("%s->lastpkt_recv_time=%f", p->p_dotted, p->lastpkt_recv_time); | ||
1768 | |||
1769 | /* The delay calculation is a special case. In cases where the | 1771 | /* The delay calculation is a special case. In cases where the |
1770 | * server and client clocks are running at different rates and | 1772 | * server and client clocks are running at different rates and |
1771 | * with very fast networks, the delay can appear negative. In | 1773 | * with very fast networks, the delay can appear negative. In |
1772 | * order to avoid violating the Principle of Least Astonishment, | 1774 | * order to avoid violating the Principle of Least Astonishment, |
1773 | * the delay is clamped not less than the system precision. | 1775 | * the delay is clamped not less than the system precision. |
1774 | */ | 1776 | */ |
1775 | dv = p->lastpkt_delay; | 1777 | delay = (T4 - T1) - (T3 - T2); |
1776 | p->lastpkt_delay = (T4 - T1) - (T3 - T2); | 1778 | if (delay < G_precision_sec) |
1777 | if (p->lastpkt_delay < G_precision_sec) | 1779 | delay = G_precision_sec; |
1778 | p->lastpkt_delay = G_precision_sec; | ||
1779 | /* | 1780 | /* |
1780 | * If this packet's delay is much bigger than the last one, | 1781 | * If this packet's delay is much bigger than the last one, |
1781 | * it's better to just ignore it than use its much less precise value. | 1782 | * it's better to just ignore it than use its much less precise value. |
1782 | */ | 1783 | */ |
1783 | if (p->reachable_bits && p->lastpkt_delay > dv * BAD_DELAY_GROWTH) { | 1784 | prev_delay = p->p_raw_delay; |
1784 | bb_error_msg("reply from %s: delay %f is too high, ignoring", p->p_dotted, p->lastpkt_delay); | 1785 | p->p_raw_delay = delay; |
1786 | if (p->reachable_bits && delay > prev_delay * BAD_DELAY_GROWTH) { | ||
1787 | bb_error_msg("reply from %s: delay %f is too high, ignoring", p->p_dotted, delay); | ||
1785 | goto pick_normal_interval; | 1788 | goto pick_normal_interval; |
1786 | } | 1789 | } |
1787 | 1790 | ||
1791 | p->lastpkt_delay = delay; | ||
1792 | p->lastpkt_recv_time = T4; | ||
1793 | VERB6 bb_error_msg("%s->lastpkt_recv_time=%f", p->p_dotted, p->lastpkt_recv_time); | ||
1794 | p->lastpkt_status = msg.m_status; | ||
1795 | p->lastpkt_stratum = msg.m_stratum; | ||
1796 | p->lastpkt_rootdelay = sfp_to_d(msg.m_rootdelay); | ||
1797 | p->lastpkt_rootdisp = sfp_to_d(msg.m_rootdisp); | ||
1798 | p->lastpkt_refid = msg.m_refid; | ||
1799 | |||
1788 | p->datapoint_idx = p->reachable_bits ? (p->datapoint_idx + 1) % NUM_DATAPOINTS : 0; | 1800 | p->datapoint_idx = p->reachable_bits ? (p->datapoint_idx + 1) % NUM_DATAPOINTS : 0; |
1789 | datapoint = &p->filter_datapoint[p->datapoint_idx]; | 1801 | datapoint = &p->filter_datapoint[p->datapoint_idx]; |
1790 | datapoint->d_recv_time = T4; | 1802 | datapoint->d_recv_time = T4; |
@@ -2087,14 +2099,34 @@ static NOINLINE void ntp_init(char **argv) | |||
2087 | "d" /* compat */ | 2099 | "d" /* compat */ |
2088 | "46aAbgL", /* compat, ignored */ | 2100 | "46aAbgL", /* compat, ignored */ |
2089 | &peers, &G.script_name, &G.verbose); | 2101 | &peers, &G.script_name, &G.verbose); |
2090 | if (!(opts & (OPT_p|OPT_l))) | 2102 | |
2091 | bb_show_usage(); | ||
2092 | // if (opts & OPT_x) /* disable stepping, only slew is allowed */ | 2103 | // if (opts & OPT_x) /* disable stepping, only slew is allowed */ |
2093 | // G.time_was_stepped = 1; | 2104 | // G.time_was_stepped = 1; |
2094 | if (peers) { | 2105 | if (peers) { |
2095 | while (peers) | 2106 | while (peers) |
2096 | add_peers(llist_pop(&peers)); | 2107 | add_peers(llist_pop(&peers)); |
2097 | } else { | 2108 | } |
2109 | #if ENABLE_FEATURE_NTPD_CONF | ||
2110 | else { | ||
2111 | parser_t *parser; | ||
2112 | char *token[3]; | ||
2113 | |||
2114 | parser = config_open("/etc/ntp.conf"); | ||
2115 | while (config_read(parser, token, 3, 1, "# \t", PARSE_NORMAL)) { | ||
2116 | if (strcmp(token[0], "server") == 0 && token[1]) { | ||
2117 | add_peers(token[1]); | ||
2118 | continue; | ||
2119 | } | ||
2120 | bb_error_msg("skipping %s:%u: unimplemented command '%s'", | ||
2121 | "/etc/ntp.conf", parser->lineno, token[0] | ||
2122 | ); | ||
2123 | } | ||
2124 | config_close(parser); | ||
2125 | } | ||
2126 | #endif | ||
2127 | if (G.peer_cnt == 0) { | ||
2128 | if (!(opts & OPT_l)) | ||
2129 | bb_show_usage(); | ||
2098 | /* -l but no peers: "stratum 1 server" mode */ | 2130 | /* -l but no peers: "stratum 1 server" mode */ |
2099 | G.stratum = 1; | 2131 | G.stratum = 1; |
2100 | } | 2132 | } |
diff --git a/networking/wget.c b/networking/wget.c index f13ca1c74..774accf7a 100644 --- a/networking/wget.c +++ b/networking/wget.c | |||
@@ -650,7 +650,7 @@ static void NOINLINE retrieve_file_data(FILE *dfp) | |||
650 | #if ENABLE_FEATURE_WGET_TIMEOUT | 650 | #if ENABLE_FEATURE_WGET_TIMEOUT |
651 | second_cnt = G.timeout_seconds; | 651 | second_cnt = G.timeout_seconds; |
652 | #endif | 652 | #endif |
653 | continue; | 653 | goto bump; |
654 | } | 654 | } |
655 | 655 | ||
656 | /* n <= 0. | 656 | /* n <= 0. |
@@ -683,11 +683,12 @@ static void NOINLINE retrieve_file_data(FILE *dfp) | |||
683 | * to try reading anyway. | 683 | * to try reading anyway. |
684 | */ | 684 | */ |
685 | } | 685 | } |
686 | #endif | ||
687 | bump: | ||
686 | /* Need to do it _every_ second for "stalled" indicator | 688 | /* Need to do it _every_ second for "stalled" indicator |
687 | * to be shown properly. | 689 | * to be shown properly. |
688 | */ | 690 | */ |
689 | progress_meter(PROGRESS_BUMP); | 691 | progress_meter(PROGRESS_BUMP); |
690 | #endif | ||
691 | } /* while (reading data) */ | 692 | } /* while (reading data) */ |
692 | 693 | ||
693 | #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT | 694 | #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT |
diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 5685b5bcc..5eac45f91 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build | |||
@@ -255,8 +255,9 @@ $(sort $(subdir-obj-y)): $(subdir-ym) ; | |||
255 | ifdef builtin-target | 255 | ifdef builtin-target |
256 | quiet_cmd_link_o_target = LD $@ | 256 | quiet_cmd_link_o_target = LD $@ |
257 | # If the list of objects to link is empty, just create an empty built-in.o | 257 | # If the list of objects to link is empty, just create an empty built-in.o |
258 | # -nostdlib is added to make "make LD=gcc ..." work (some people use that) | ||
258 | cmd_link_o_target = $(if $(strip $(obj-y)),\ | 259 | cmd_link_o_target = $(if $(strip $(obj-y)),\ |
259 | $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^),\ | 260 | $(LD) -nostdlib $(ld_flags) -r -o $@ $(filter $(obj-y), $^),\ |
260 | rm -f $@; $(AR) rcs $@) | 261 | rm -f $@; $(AR) rcs $@) |
261 | 262 | ||
262 | $(builtin-target): $(obj-y) FORCE | 263 | $(builtin-target): $(obj-y) FORCE |
diff --git a/scripts/gen_build_files.sh b/scripts/gen_build_files.sh index e8fa831be..ebee17c64 100755 --- a/scripts/gen_build_files.sh +++ b/scripts/gen_build_files.sh | |||
@@ -71,7 +71,7 @@ sed -n -e 's@^//usage:\([ '"$TAB"'].*\)$@\1 \\@p' \ | |||
71 | 71 | ||
72 | # (Re)generate */Kbuild and */Config.in | 72 | # (Re)generate */Kbuild and */Config.in |
73 | # We skip .dotdirs - makes git/svn/etc users happier | 73 | # We skip .dotdirs - makes git/svn/etc users happier |
74 | { cd -- "$srctree" && find . -type d -not '(' -name '.?*' -prune ')'; } \ | 74 | { cd -- "$srctree" && find . -type d ! '(' -name '.?*' -prune ')'; } \ |
75 | | while read -r d; do | 75 | | while read -r d; do |
76 | d="${d#./}" | 76 | d="${d#./}" |
77 | 77 | ||
diff --git a/scripts/trylink b/scripts/trylink index e47169917..5da494fbb 100755 --- a/scripts/trylink +++ b/scripts/trylink | |||
@@ -268,7 +268,7 @@ fi | |||
268 | 268 | ||
269 | if test "$CONFIG_FEATURE_INDIVIDUAL" = y; then | 269 | if test "$CONFIG_FEATURE_INDIVIDUAL" = y; then |
270 | echo "Linking individual applets against libbusybox (see $sharedlib_dir/*)" | 270 | echo "Linking individual applets against libbusybox (see $sharedlib_dir/*)" |
271 | gcc -DNAME_MAIN_CNAME -E -include include/autoconf.h include/applets.h \ | 271 | gcc -DNAME_MAIN -E -include include/autoconf.h include/applets.h \ |
272 | | grep -v "^#" \ | 272 | | grep -v "^#" \ |
273 | | grep -v "^$" \ | 273 | | grep -v "^$" \ |
274 | > applet_lst.tmp | 274 | > applet_lst.tmp |
@@ -300,6 +300,8 @@ int main(int argc, char **argv) | |||
300 | } | 300 | } |
301 | rm -- "$sharedlib_dir/applet.c" $EXE.out | 301 | rm -- "$sharedlib_dir/applet.c" $EXE.out |
302 | $STRIP -s --remove-section=.note --remove-section=.comment $EXE | 302 | $STRIP -s --remove-section=.note --remove-section=.comment $EXE |
303 | # Let user see that we do something - list the names of created binaries: | ||
304 | echo "$EXE" | ||
303 | 305 | ||
304 | done <applet_lst.tmp | 306 | done <applet_lst.tmp |
305 | fi | 307 | fi |
diff --git a/shell/ash.c b/shell/ash.c index 19565d185..ce6e355eb 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -55,6 +55,7 @@ | |||
55 | #include <setjmp.h> | 55 | #include <setjmp.h> |
56 | #include <fnmatch.h> | 56 | #include <fnmatch.h> |
57 | #include <sys/times.h> | 57 | #include <sys/times.h> |
58 | #include <sys/utsname.h> /* for setting $HOSTNAME */ | ||
58 | 59 | ||
59 | #include "busybox.h" /* for applet_names */ | 60 | #include "busybox.h" /* for applet_names */ |
60 | #include "unicode.h" | 61 | #include "unicode.h" |
@@ -9353,6 +9354,9 @@ mklocal(char *name) | |||
9353 | vp->flags |= VSTRFIXED|VTEXTFIXED; | 9354 | vp->flags |= VSTRFIXED|VTEXTFIXED; |
9354 | if (eq) | 9355 | if (eq) |
9355 | setvareq(name, 0); | 9356 | setvareq(name, 0); |
9357 | else | ||
9358 | /* "local VAR" unsets VAR: */ | ||
9359 | setvar(name, NULL, 0); | ||
9356 | } | 9360 | } |
9357 | } | 9361 | } |
9358 | lvp->vp = vp; | 9362 | lvp->vp = vp; |
@@ -12899,7 +12903,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) | |||
12899 | e = errno; | 12903 | e = errno; |
12900 | goto loop; | 12904 | goto loop; |
12901 | } | 12905 | } |
12902 | if (!execable_file(fullname)) { | 12906 | if (!file_is_executable(fullname)) { |
12903 | e = ENOEXEC; | 12907 | e = ENOEXEC; |
12904 | goto loop; | 12908 | goto loop; |
12905 | } | 12909 | } |
@@ -13522,6 +13526,11 @@ init(void) | |||
13522 | #if ENABLE_ASH_BASH_COMPAT | 13526 | #if ENABLE_ASH_BASH_COMPAT |
13523 | p = lookupvar("SHLVL"); | 13527 | p = lookupvar("SHLVL"); |
13524 | setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT); | 13528 | setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT); |
13529 | if (!lookupvar("HOSTNAME")) { | ||
13530 | struct utsname uts; | ||
13531 | uname(&uts); | ||
13532 | setvar2("HOSTNAME", uts.nodename); | ||
13533 | } | ||
13525 | #endif | 13534 | #endif |
13526 | p = lookupvar("PWD"); | 13535 | p = lookupvar("PWD"); |
13527 | if (p) { | 13536 | if (p) { |
diff --git a/shell/ash_test/ash-misc/local1.right b/shell/ash_test/ash-misc/local1.right new file mode 100644 index 000000000..a2d121df6 --- /dev/null +++ b/shell/ash_test/ash-misc/local1.right | |||
@@ -0,0 +1,4 @@ | |||
1 | A1:'A' | ||
2 | A2:'' | ||
3 | A3:'' | ||
4 | A4:'A' | ||
diff --git a/shell/ash_test/ash-misc/local1.tests b/shell/ash_test/ash-misc/local1.tests new file mode 100755 index 000000000..b1e675059 --- /dev/null +++ b/shell/ash_test/ash-misc/local1.tests | |||
@@ -0,0 +1,11 @@ | |||
1 | a=A | ||
2 | f() { | ||
3 | local a | ||
4 | # the above line unsets $a | ||
5 | echo "A2:'$a'" | ||
6 | unset a | ||
7 | echo "A3:'$a'" | ||
8 | } | ||
9 | echo "A1:'$a'" | ||
10 | f | ||
11 | echo "A4:'$a'" | ||
diff --git a/shell/hush.c b/shell/hush.c index 927193450..e1d0ece29 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -91,6 +91,7 @@ | |||
91 | #if ENABLE_HUSH_CASE | 91 | #if ENABLE_HUSH_CASE |
92 | # include <fnmatch.h> | 92 | # include <fnmatch.h> |
93 | #endif | 93 | #endif |
94 | #include <sys/utsname.h> /* for setting $HOSTNAME */ | ||
94 | 95 | ||
95 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ | 96 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ |
96 | #include "unicode.h" | 97 | #include "unicode.h" |
@@ -944,6 +945,7 @@ static const struct built_in_command bltins1[] = { | |||
944 | BLTIN("source" , builtin_source , "Run commands in a file"), | 945 | BLTIN("source" , builtin_source , "Run commands in a file"), |
945 | #endif | 946 | #endif |
946 | BLTIN("trap" , builtin_trap , "Trap signals"), | 947 | BLTIN("trap" , builtin_trap , "Trap signals"), |
948 | BLTIN("true" , builtin_true , NULL), | ||
947 | BLTIN("type" , builtin_type , "Show command type"), | 949 | BLTIN("type" , builtin_type , "Show command type"), |
948 | BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), | 950 | BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), |
949 | BLTIN("umask" , builtin_umask , "Set file creation mask"), | 951 | BLTIN("umask" , builtin_umask , "Set file creation mask"), |
@@ -7785,6 +7787,14 @@ int hush_main(int argc, char **argv) | |||
7785 | 7787 | ||
7786 | /* Export PWD */ | 7788 | /* Export PWD */ |
7787 | set_pwd_var(/*exp:*/ 1); | 7789 | set_pwd_var(/*exp:*/ 1); |
7790 | |||
7791 | #if ENABLE_HUSH_BASH_COMPAT | ||
7792 | /* Set (but not export) HOSTNAME unless already set */ | ||
7793 | if (!get_local_var_value("HOSTNAME")) { | ||
7794 | struct utsname uts; | ||
7795 | uname(&uts); | ||
7796 | set_local_var_from_halves("HOSTNAME", uts.nodename); | ||
7797 | } | ||
7788 | /* bash also exports SHLVL and _, | 7798 | /* bash also exports SHLVL and _, |
7789 | * and sets (but doesn't export) the following variables: | 7799 | * and sets (but doesn't export) the following variables: |
7790 | * BASH=/bin/bash | 7800 | * BASH=/bin/bash |
@@ -7793,7 +7803,6 @@ int hush_main(int argc, char **argv) | |||
7793 | * HOSTTYPE=i386 | 7803 | * HOSTTYPE=i386 |
7794 | * MACHTYPE=i386-pc-linux-gnu | 7804 | * MACHTYPE=i386-pc-linux-gnu |
7795 | * OSTYPE=linux-gnu | 7805 | * OSTYPE=linux-gnu |
7796 | * HOSTNAME=<xxxxxxxxxx> | ||
7797 | * PPID=<NNNNN> - we also do it elsewhere | 7806 | * PPID=<NNNNN> - we also do it elsewhere |
7798 | * EUID=<NNNNN> | 7807 | * EUID=<NNNNN> |
7799 | * UID=<NNNNN> | 7808 | * UID=<NNNNN> |
@@ -7821,6 +7830,7 @@ int hush_main(int argc, char **argv) | |||
7821 | * PS2='> ' | 7830 | * PS2='> ' |
7822 | * PS4='+ ' | 7831 | * PS4='+ ' |
7823 | */ | 7832 | */ |
7833 | #endif | ||
7824 | 7834 | ||
7825 | #if ENABLE_FEATURE_EDITING | 7835 | #if ENABLE_FEATURE_EDITING |
7826 | G.line_input_state = new_line_input_t(FOR_SHELL); | 7836 | G.line_input_state = new_line_input_t(FOR_SHELL); |
diff --git a/shell/random.c b/shell/random.c index 853ab085a..5d3620516 100644 --- a/shell/random.c +++ b/shell/random.c | |||
@@ -6,17 +6,51 @@ | |||
6 | * | 6 | * |
7 | * Licensed under GPLv2, see file LICENSE in this source tree. | 7 | * Licensed under GPLv2, see file LICENSE in this source tree. |
8 | */ | 8 | */ |
9 | #include "libbb.h" | 9 | |
10 | #include "random.h" | 10 | /* For testing against dieharder, you need only random.{c,h} |
11 | * Howto: | ||
12 | * gcc -O2 -Wall -DRANDTEST random.c -o random | ||
13 | * ./random | dieharder -g 200 -a | ||
14 | */ | ||
15 | |||
16 | #if !defined RANDTEST | ||
17 | |||
18 | # include "libbb.h" | ||
19 | # include "random.h" | ||
20 | # define RAND_BASH_MASK 0x7fff | ||
21 | |||
22 | #else | ||
23 | # include <stdint.h> | ||
24 | # include <unistd.h> | ||
25 | # include <stdio.h> | ||
26 | # include <time.h> | ||
27 | # define FAST_FUNC /* nothing */ | ||
28 | # define PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN /* nothing */ | ||
29 | # define POP_SAVED_FUNCTION_VISIBILITY /* nothing */ | ||
30 | # define monotonic_us() time(NULL) | ||
31 | # include "random.h" | ||
32 | # define RAND_BASH_MASK 0xffffffff /* off */ | ||
33 | #endif | ||
11 | 34 | ||
12 | uint32_t FAST_FUNC | 35 | uint32_t FAST_FUNC |
13 | next_random(random_t *rnd) | 36 | next_random(random_t *rnd) |
14 | { | 37 | { |
15 | /* Galois LFSR parameter */ | 38 | /* Galois LFSR parameter: |
16 | /* Taps at 32 31 29 1: */ | 39 | * Taps at 32 31 29 1: |
40 | */ | ||
17 | enum { MASK = 0x8000000b }; | 41 | enum { MASK = 0x8000000b }; |
18 | /* Another example - taps at 32 31 30 10: */ | 42 | /* Another example - taps at 32 31 30 10: */ |
19 | /* MASK = 0x00400007 */ | 43 | /* enum { MASK = 0x00400007 }; */ |
44 | |||
45 | /* Xorshift parameters: | ||
46 | * Choices for a,b,c: 10,13,10; 8,9,22; 2,7,3; 23,3,24 | ||
47 | * (given by algorithm author) | ||
48 | */ | ||
49 | enum { | ||
50 | a = 2, | ||
51 | b = 7, | ||
52 | c = 3, | ||
53 | }; | ||
20 | 54 | ||
21 | uint32_t t; | 55 | uint32_t t; |
22 | 56 | ||
@@ -27,18 +61,100 @@ next_random(random_t *rnd) | |||
27 | INIT_RANDOM_T(rnd, getpid(), monotonic_us()); | 61 | INIT_RANDOM_T(rnd, getpid(), monotonic_us()); |
28 | } | 62 | } |
29 | 63 | ||
30 | /* LCG has period of 2^32 and alternating lowest bit */ | 64 | /* LCG: period of 2^32, but quite weak: |
65 | * bit 0 alternates beetween 0 and 1 (pattern of length 2) | ||
66 | * bit 1 has a repeating pattern of length 4 | ||
67 | * bit 2 has a repeating pattern of length 8 | ||
68 | * etc... | ||
69 | */ | ||
31 | rnd->LCG = 1664525 * rnd->LCG + 1013904223; | 70 | rnd->LCG = 1664525 * rnd->LCG + 1013904223; |
32 | /* Galois LFSR has period of 2^32-1 = 3 * 5 * 17 * 257 * 65537 */ | 71 | |
72 | /* Galois LFSR: | ||
73 | * period of 2^32-1 = 3 * 5 * 17 * 257 * 65537. | ||
74 | * Successive values are right-shifted one bit | ||
75 | * and possibly xored with a sparse constant. | ||
76 | */ | ||
33 | t = (rnd->galois_LFSR << 1); | 77 | t = (rnd->galois_LFSR << 1); |
34 | if (rnd->galois_LFSR < 0) /* if we just shifted 1 out of msb... */ | 78 | if (rnd->galois_LFSR < 0) /* if we just shifted 1 out of msb... */ |
35 | t ^= MASK; | 79 | t ^= MASK; |
36 | rnd->galois_LFSR = t; | 80 | rnd->galois_LFSR = t; |
37 | /* Both are weak, combining them gives better randomness | ||
38 | * and ~2^64 period. & 0x7fff is probably bash compat | ||
39 | * for $RANDOM range. Combining with subtraction is | ||
40 | * just for fun. + and ^ would work equally well. */ | ||
41 | t = (t - rnd->LCG) & 0x7fff; | ||
42 | 81 | ||
43 | return t; | 82 | /* http://en.wikipedia.org/wiki/Xorshift |
83 | * Moderately good statistical properties: | ||
84 | * fails the following "dieharder -g 200 -a" tests: | ||
85 | * diehard_operm5| 0 | ||
86 | * diehard_oqso| 0 | ||
87 | * diehard_count_1s_byt| 0 | ||
88 | * diehard_3dsphere| 3 | ||
89 | * diehard_squeeze| 0 | ||
90 | * diehard_runs| 0 | ||
91 | * diehard_runs| 0 | ||
92 | * diehard_craps| 0 | ||
93 | * diehard_craps| 0 | ||
94 | * rgb_minimum_distance| 3 | ||
95 | * rgb_minimum_distance| 4 | ||
96 | * rgb_minimum_distance| 5 | ||
97 | * rgb_permutations| 3 | ||
98 | * rgb_permutations| 4 | ||
99 | * rgb_permutations| 5 | ||
100 | * dab_filltree| 32 | ||
101 | * dab_filltree| 32 | ||
102 | * dab_monobit2| 12 | ||
103 | */ | ||
104 | again: | ||
105 | t = rnd->xs64_x ^ (rnd->xs64_x << a); | ||
106 | rnd->xs64_x = rnd->xs64_y; | ||
107 | rnd->xs64_y = rnd->xs64_y ^ (rnd->xs64_y >> c) ^ t ^ (t >> b); | ||
108 | /* | ||
109 | * Period 2^64-1 = 2^32+1 * 2^32-1 has a common divisor with Galois LFSR. | ||
110 | * By skipping two possible states (0x1 and 0x2) we reduce period to | ||
111 | * 2^64-3 = 13 * 3889 * 364870227143809 which has no common divisors: | ||
112 | */ | ||
113 | if (rnd->xs64_y == 0 && rnd->xs64_x <= 2) | ||
114 | goto again; | ||
115 | |||
116 | /* Combined LCG + Galois LFSR rng has 2^32 * 2^32-1 period. | ||
117 | * Strength: | ||
118 | * individually, both are extremely weak cryptographycally; | ||
119 | * when combined, they fail the following "dieharder -g 200 -a" tests: | ||
120 | * diehard_rank_6x8| 0 | ||
121 | * diehard_oqso| 0 | ||
122 | * diehard_dna| 0 | ||
123 | * diehard_count_1s_byt| 0 | ||
124 | * rgb_bitdist| 2 | ||
125 | * dab_monobit2| 12 | ||
126 | * | ||
127 | * Combining them with xorshift-64 increases period to | ||
128 | * 2^32 * 2^32-1 * 2^64-3 | ||
129 | * which is about 2^128, or in base 10 ~3.40*10^38. | ||
130 | * Strength of the combination: | ||
131 | * passes all "dieharder -g 200 -a" tests. | ||
132 | * | ||
133 | * Combining with subtraction and addition is just for fun. | ||
134 | * It does not add meaningful strength, could use xor operation instead. | ||
135 | */ | ||
136 | t = rnd->galois_LFSR - rnd->LCG + rnd->xs64_y; | ||
137 | |||
138 | /* bash compat $RANDOM range: */ | ||
139 | return t & RAND_BASH_MASK; | ||
44 | } | 140 | } |
141 | |||
142 | #ifdef RANDTEST | ||
143 | static random_t rnd; | ||
144 | |||
145 | int main(int argc, char **argv) | ||
146 | { | ||
147 | int i; | ||
148 | uint32_t buf[4096]; | ||
149 | |||
150 | for (;;) { | ||
151 | for (i = 0; i < sizeof(buf) / sizeof(buf[0]); i++) { | ||
152 | buf[i] = next_random(&rnd); | ||
153 | } | ||
154 | write(1, buf, sizeof(buf)); | ||
155 | } | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | #endif | ||
diff --git a/shell/random.h b/shell/random.h index 180c48abb..c4eb44c13 100644 --- a/shell/random.h +++ b/shell/random.h | |||
@@ -12,16 +12,24 @@ | |||
12 | PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN | 12 | PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN |
13 | 13 | ||
14 | typedef struct random_t { | 14 | typedef struct random_t { |
15 | /* Random number generators */ | 15 | /* State of random number generators: */ |
16 | int32_t galois_LFSR; /* Galois LFSR (fast but weak). signed! */ | 16 | |
17 | uint32_t LCG; /* LCG (fast but weak) */ | 17 | /* Galois LFSR (fast but weak) */ |
18 | int32_t galois_LFSR; /* must be signed! */ | ||
19 | |||
20 | /* LCG (fast but weak) */ | ||
21 | uint32_t LCG; | ||
22 | |||
23 | /* 64-bit xorshift (fast, moderate strength) */ | ||
24 | uint32_t xs64_x; | ||
25 | uint32_t xs64_y; | ||
18 | } random_t; | 26 | } random_t; |
19 | 27 | ||
20 | #define UNINITED_RANDOM_T(rnd) \ | 28 | #define UNINITED_RANDOM_T(rnd) \ |
21 | ((rnd)->galois_LFSR == 0) | 29 | ((rnd)->galois_LFSR == 0) |
22 | 30 | ||
23 | #define INIT_RANDOM_T(rnd, nonzero, v) \ | 31 | #define INIT_RANDOM_T(rnd, nonzero, v) \ |
24 | ((rnd)->galois_LFSR = (nonzero), (rnd)->LCG = (v)) | 32 | ((rnd)->galois_LFSR = (rnd)->xs64_x = (nonzero), (rnd)->LCG = (rnd)->xs64_y = (v)) |
25 | 33 | ||
26 | #define CLEAR_RANDOM_T(rnd) \ | 34 | #define CLEAR_RANDOM_T(rnd) \ |
27 | ((rnd)->galois_LFSR = 0) | 35 | ((rnd)->galois_LFSR = 0) |
diff --git a/util-linux/Config.src b/util-linux/Config.src index 5a8b0063b..c1cd6daa4 100644 --- a/util-linux/Config.src +++ b/util-linux/Config.src | |||
@@ -599,6 +599,15 @@ config SWAPONOFF | |||
599 | space. If you are not using any swap space, you can leave this | 599 | space. If you are not using any swap space, you can leave this |
600 | option disabled. | 600 | option disabled. |
601 | 601 | ||
602 | config FEATURE_SWAPON_DISCARD | ||
603 | bool "Support discard option -d" | ||
604 | default y | ||
605 | depends on SWAPONOFF | ||
606 | help | ||
607 | Enable support for discarding swap area blocks at swapon and/or as | ||
608 | the kernel frees them. This option enables both the -d option on | ||
609 | 'swapon' and the 'discard' option for swap entries in /etc/fstab. | ||
610 | |||
602 | config FEATURE_SWAPON_PRI | 611 | config FEATURE_SWAPON_PRI |
603 | bool "Support priority option -p" | 612 | bool "Support priority option -p" |
604 | default y | 613 | default y |
diff --git a/util-linux/rtcwake.c b/util-linux/rtcwake.c index 735a29822..33cdbfad4 100644 --- a/util-linux/rtcwake.c +++ b/util-linux/rtcwake.c | |||
@@ -51,7 +51,6 @@ | |||
51 | 51 | ||
52 | #define SYS_RTC_PATH "/sys/class/rtc/%s/device/power/wakeup" | 52 | #define SYS_RTC_PATH "/sys/class/rtc/%s/device/power/wakeup" |
53 | #define SYS_POWER_PATH "/sys/power/state" | 53 | #define SYS_POWER_PATH "/sys/power/state" |
54 | #define DEFAULT_MODE "standby" | ||
55 | 54 | ||
56 | static NOINLINE bool may_wakeup(const char *rtcname) | 55 | static NOINLINE bool may_wakeup(const char *rtcname) |
57 | { | 56 | { |
@@ -122,17 +121,16 @@ static NOINLINE void setup_alarm(int fd, time_t *wakeup, time_t rtc_time) | |||
122 | int rtcwake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 121 | int rtcwake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
123 | int rtcwake_main(int argc UNUSED_PARAM, char **argv) | 122 | int rtcwake_main(int argc UNUSED_PARAM, char **argv) |
124 | { | 123 | { |
125 | time_t rtc_time; | ||
126 | |||
127 | unsigned opt; | 124 | unsigned opt; |
128 | const char *rtcname = NULL; | 125 | const char *rtcname = NULL; |
129 | const char *suspend; | 126 | const char *suspend = "standby"; |
130 | const char *opt_seconds; | 127 | const char *opt_seconds; |
131 | const char *opt_time; | 128 | const char *opt_time; |
132 | 129 | ||
130 | time_t rtc_time; | ||
133 | time_t sys_time; | 131 | time_t sys_time; |
134 | time_t alarm_time = 0; | 132 | time_t alarm_time = alarm_time; |
135 | unsigned seconds = 0; | 133 | unsigned seconds = seconds; /* for compiler */ |
136 | int utc = -1; | 134 | int utc = -1; |
137 | int fd; | 135 | int fd; |
138 | 136 | ||
@@ -148,6 +146,8 @@ int rtcwake_main(int argc UNUSED_PARAM, char **argv) | |||
148 | ; | 146 | ; |
149 | applet_long_options = rtcwake_longopts; | 147 | applet_long_options = rtcwake_longopts; |
150 | #endif | 148 | #endif |
149 | /* Must have -s or -t, exclusive */ | ||
150 | opt_complementary = "s:t:s--t:t--s"; | ||
151 | opt = getopt32(argv, "alud:m:s:t:", &rtcname, &suspend, &opt_seconds, &opt_time); | 151 | opt = getopt32(argv, "alud:m:s:t:", &rtcname, &suspend, &opt_seconds, &opt_time); |
152 | 152 | ||
153 | /* this is the default | 153 | /* this is the default |
@@ -156,17 +156,17 @@ int rtcwake_main(int argc UNUSED_PARAM, char **argv) | |||
156 | */ | 156 | */ |
157 | if (opt & (RTCWAKE_OPT_UTC | RTCWAKE_OPT_LOCAL)) | 157 | if (opt & (RTCWAKE_OPT_UTC | RTCWAKE_OPT_LOCAL)) |
158 | utc = opt & RTCWAKE_OPT_UTC; | 158 | utc = opt & RTCWAKE_OPT_UTC; |
159 | if (!(opt & RTCWAKE_OPT_SUSPEND_MODE)) | 159 | if (opt & RTCWAKE_OPT_SECONDS) { |
160 | suspend = DEFAULT_MODE; | ||
161 | if (opt & RTCWAKE_OPT_SECONDS) | ||
162 | /* alarm time, seconds-to-sleep (relative) */ | 160 | /* alarm time, seconds-to-sleep (relative) */ |
163 | seconds = xatoi(opt_seconds); | 161 | seconds = xatou(opt_seconds); |
164 | if (opt & RTCWAKE_OPT_TIME) | 162 | } else { |
163 | /* RTCWAKE_OPT_TIME */ | ||
165 | /* alarm time, time_t (absolute, seconds since 1/1 1970 UTC) */ | 164 | /* alarm time, time_t (absolute, seconds since 1/1 1970 UTC) */ |
166 | alarm_time = xatol(opt_time); | 165 | if (sizeof(alarm_time) <= sizeof(long)) |
167 | 166 | alarm_time = xatol(opt_time); | |
168 | if (!alarm_time && !seconds) | 167 | else |
169 | bb_error_msg_and_die("must provide wake time"); | 168 | alarm_time = xatoll(opt_time); |
169 | } | ||
170 | 170 | ||
171 | if (utc == -1) | 171 | if (utc == -1) |
172 | utc = rtc_adjtime_is_utc(); | 172 | utc = rtc_adjtime_is_utc(); |
@@ -177,8 +177,9 @@ int rtcwake_main(int argc UNUSED_PARAM, char **argv) | |||
177 | /* this RTC must exist and (if we'll sleep) be wakeup-enabled */ | 177 | /* this RTC must exist and (if we'll sleep) be wakeup-enabled */ |
178 | fd = rtc_xopen(&rtcname, O_RDONLY); | 178 | fd = rtc_xopen(&rtcname, O_RDONLY); |
179 | 179 | ||
180 | if (strcmp(suspend, "on") && !may_wakeup(rtcname)) | 180 | if (strcmp(suspend, "on") != 0) |
181 | bb_error_msg_and_die("%s not enabled for wakeup events", rtcname); | 181 | if (!may_wakeup(rtcname)) |
182 | bb_error_msg_and_die("%s not enabled for wakeup events", rtcname); | ||
182 | 183 | ||
183 | /* relative or absolute alarm time, normalized to time_t */ | 184 | /* relative or absolute alarm time, normalized to time_t */ |
184 | sys_time = time(NULL); | 185 | sys_time = time(NULL); |
@@ -188,21 +189,29 @@ int rtcwake_main(int argc UNUSED_PARAM, char **argv) | |||
188 | rtc_time = rtc_tm2time(&tm_time, utc); | 189 | rtc_time = rtc_tm2time(&tm_time, utc); |
189 | } | 190 | } |
190 | 191 | ||
191 | 192 | if (opt & RTCWAKE_OPT_TIME) { | |
192 | if (alarm_time) { | 193 | /* Correct for RTC<->system clock difference */ |
193 | if (alarm_time < sys_time) | 194 | alarm_time += rtc_time - sys_time; |
195 | if (alarm_time < rtc_time) | ||
196 | /* | ||
197 | * Compat message text. | ||
198 | * I'd say "RTC time is already ahead of ..." instead. | ||
199 | */ | ||
194 | bb_error_msg_and_die("time doesn't go backward to %s", ctime(&alarm_time)); | 200 | bb_error_msg_and_die("time doesn't go backward to %s", ctime(&alarm_time)); |
195 | alarm_time += sys_time - rtc_time; | ||
196 | } else | 201 | } else |
197 | alarm_time = rtc_time + seconds + 1; | 202 | alarm_time = rtc_time + seconds + 1; |
198 | setup_alarm(fd, &alarm_time, rtc_time); | ||
199 | 203 | ||
204 | setup_alarm(fd, &alarm_time, rtc_time); | ||
200 | sync(); | 205 | sync(); |
206 | #if 0 /*debug*/ | ||
207 | printf("sys_time: %s", ctime(&sys_time)); | ||
208 | printf("rtc_time: %s", ctime(&rtc_time)); | ||
209 | #endif | ||
201 | printf("wakeup from \"%s\" at %s", suspend, ctime(&alarm_time)); | 210 | printf("wakeup from \"%s\" at %s", suspend, ctime(&alarm_time)); |
202 | fflush_all(); | 211 | fflush_all(); |
203 | usleep(10 * 1000); | 212 | usleep(10 * 1000); |
204 | 213 | ||
205 | if (strcmp(suspend, "on")) | 214 | if (strcmp(suspend, "on") != 0) |
206 | xopen_xwrite_close(SYS_POWER_PATH, suspend); | 215 | xopen_xwrite_close(SYS_POWER_PATH, suspend); |
207 | else { | 216 | else { |
208 | /* "fake" suspend ... we'll do the delay ourselves */ | 217 | /* "fake" suspend ... we'll do the delay ourselves */ |
diff --git a/util-linux/script.c b/util-linux/script.c index 8fb991d15..abcd73bff 100644 --- a/util-linux/script.c +++ b/util-linux/script.c | |||
@@ -77,8 +77,15 @@ int script_main(int argc UNUSED_PARAM, char **argv) | |||
77 | if (!(opt & OPT_q)) { | 77 | if (!(opt & OPT_q)) { |
78 | printf("Script started, file is %s\n", fname); | 78 | printf("Script started, file is %s\n", fname); |
79 | } | 79 | } |
80 | |||
80 | shell = get_shell_name(); | 81 | shell = get_shell_name(); |
81 | 82 | ||
83 | /* Some people run "script ... 0>&-". | ||
84 | * Our code assumes that STDIN_FILENO != pty. | ||
85 | * Ensure STDIN_FILENO is not closed: | ||
86 | */ | ||
87 | bb_sanitize_stdio(); | ||
88 | |||
82 | pty = xgetpty(pty_line); | 89 | pty = xgetpty(pty_line); |
83 | 90 | ||
84 | /* get current stdin's tty params */ | 91 | /* get current stdin's tty params */ |
diff --git a/util-linux/swaponoff.c b/util-linux/swaponoff.c index 3f223343e..75487267b 100644 --- a/util-linux/swaponoff.c +++ b/util-linux/swaponoff.c | |||
@@ -8,10 +8,14 @@ | |||
8 | */ | 8 | */ |
9 | 9 | ||
10 | //usage:#define swapon_trivial_usage | 10 | //usage:#define swapon_trivial_usage |
11 | //usage: "[-a]" IF_FEATURE_SWAPON_PRI(" [-p PRI]") " [DEVICE]" | 11 | //usage: "[-a]" IF_FEATURE_SWAPON_DISCARD(" [-d[POL]]") IF_FEATURE_SWAPON_PRI(" [-p PRI]") " [DEVICE]" |
12 | //usage:#define swapon_full_usage "\n\n" | 12 | //usage:#define swapon_full_usage "\n\n" |
13 | //usage: "Start swapping on DEVICE\n" | 13 | //usage: "Start swapping on DEVICE\n" |
14 | //usage: "\n -a Start swapping on all swap devices" | 14 | //usage: "\n -a Start swapping on all swap devices" |
15 | //usage: IF_FEATURE_SWAPON_DISCARD( | ||
16 | //usage: "\n -d[POL] Discard blocks at swapon (POL=once)," | ||
17 | //usage: "\n as freed (POL=pages), or both (POL omitted)" | ||
18 | //usage: ) | ||
15 | //usage: IF_FEATURE_SWAPON_PRI( | 19 | //usage: IF_FEATURE_SWAPON_PRI( |
16 | //usage: "\n -p PRI Set swap device priority" | 20 | //usage: "\n -p PRI Set swap device priority" |
17 | //usage: ) | 21 | //usage: ) |
@@ -38,78 +42,162 @@ | |||
38 | # define MNTTYPE_SWAP "swap" | 42 | # define MNTTYPE_SWAP "swap" |
39 | #endif | 43 | #endif |
40 | 44 | ||
41 | #if ENABLE_FEATURE_SWAPON_PRI | 45 | #if ENABLE_FEATURE_SWAPON_DISCARD |
46 | #ifndef SWAP_FLAG_DISCARD | ||
47 | #define SWAP_FLAG_DISCARD 0x10000 | ||
48 | #endif | ||
49 | #ifndef SWAP_FLAG_DISCARD_ONCE | ||
50 | #define SWAP_FLAG_DISCARD_ONCE 0x20000 | ||
51 | #endif | ||
52 | #ifndef SWAP_FLAG_DISCARD_PAGES | ||
53 | #define SWAP_FLAG_DISCARD_PAGES 0x40000 | ||
54 | #endif | ||
55 | #define SWAP_FLAG_DISCARD_MASK \ | ||
56 | (SWAP_FLAG_DISCARD | SWAP_FLAG_DISCARD_ONCE | SWAP_FLAG_DISCARD_PAGES) | ||
57 | #endif | ||
58 | |||
59 | |||
60 | #if ENABLE_FEATURE_SWAPON_DISCARD || ENABLE_FEATURE_SWAPON_PRI | ||
42 | struct globals { | 61 | struct globals { |
43 | int flags; | 62 | int flags; |
44 | } FIX_ALIASING; | 63 | } FIX_ALIASING; |
45 | #define G (*(struct globals*)&bb_common_bufsiz1) | 64 | #define G (*(struct globals*)&bb_common_bufsiz1) |
46 | #define g_flags (G.flags) | 65 | #define g_flags (G.flags) |
66 | #define save_g_flags() int save_g_flags = g_flags | ||
67 | #define restore_g_flags() g_flags = save_g_flags | ||
47 | #else | 68 | #else |
48 | #define g_flags 0 | 69 | #define g_flags 0 |
70 | #define save_g_flags() ((void)0) | ||
71 | #define restore_g_flags() ((void)0) | ||
49 | #endif | 72 | #endif |
50 | #define INIT_G() do { } while (0) | 73 | #define INIT_G() do { } while (0) |
51 | 74 | ||
75 | #define do_swapoff (applet_name[5] == 'f') | ||
76 | |||
77 | /* Command line options */ | ||
78 | enum { | ||
79 | OPTBIT_a, /* -a all */ | ||
80 | IF_FEATURE_SWAPON_DISCARD( OPTBIT_d ,) /* -d discard */ | ||
81 | IF_FEATURE_SWAPON_PRI ( OPTBIT_p ,) /* -p priority */ | ||
82 | OPT_a = 1 << OPTBIT_a, | ||
83 | OPT_d = IF_FEATURE_SWAPON_DISCARD((1 << OPTBIT_d)) + 0, | ||
84 | OPT_p = IF_FEATURE_SWAPON_PRI ((1 << OPTBIT_p)) + 0, | ||
85 | }; | ||
86 | |||
87 | #define OPT_ALL (option_mask32 & OPT_a) | ||
88 | #define OPT_DISCARD (option_mask32 & OPT_d) | ||
89 | #define OPT_PRIO (option_mask32 & OPT_p) | ||
90 | |||
52 | static int swap_enable_disable(char *device) | 91 | static int swap_enable_disable(char *device) |
53 | { | 92 | { |
54 | int status; | 93 | int err = 0; |
94 | int quiet = 0; | ||
55 | struct stat st; | 95 | struct stat st; |
56 | 96 | ||
57 | resolve_mount_spec(&device); | 97 | resolve_mount_spec(&device); |
58 | xstat(device, &st); | ||
59 | 98 | ||
60 | #if ENABLE_DESKTOP | 99 | if (do_swapoff) { |
61 | /* test for holes */ | 100 | err = swapoff(device); |
62 | if (S_ISREG(st.st_mode)) | 101 | /* Don't complain on OPT_ALL if not a swap device or if it doesn't exist */ |
63 | if (st.st_blocks * (off_t)512 < st.st_size) | 102 | quiet = (OPT_ALL && (errno == EINVAL || errno == ENOENT)); |
64 | bb_error_msg("warning: swap file has holes"); | 103 | } else { |
65 | #endif | 104 | /* swapon */ |
66 | 105 | err = stat(device, &st); | |
67 | if (applet_name[5] == 'n') | 106 | if (!err) { |
68 | status = swapon(device, g_flags); | 107 | if (ENABLE_DESKTOP && S_ISREG(st.st_mode)) { |
69 | else | 108 | if (st.st_blocks * (off_t)512 < st.st_size) { |
70 | status = swapoff(device); | 109 | bb_error_msg("%s: file has holes", device); |
110 | return 1; | ||
111 | } | ||
112 | } | ||
113 | err = swapon(device, g_flags); | ||
114 | /* Don't complain on swapon -a if device is already in use */ | ||
115 | quiet = (OPT_ALL && errno == EBUSY); | ||
116 | } | ||
117 | } | ||
71 | 118 | ||
72 | if (status != 0) { | 119 | if (err && !quiet) { |
73 | bb_simple_perror_msg(device); | 120 | bb_simple_perror_msg(device); |
74 | return 1; | 121 | return 1; |
75 | } | 122 | } |
76 | |||
77 | return 0; | 123 | return 0; |
78 | } | 124 | } |
79 | 125 | ||
80 | static int do_em_all(void) | 126 | #if ENABLE_FEATURE_SWAPON_DISCARD |
127 | static void set_discard_flag(char *s) | ||
81 | { | 128 | { |
82 | struct mntent *m; | 129 | /* Unset the flag first to allow fstab options to override */ |
83 | FILE *f; | 130 | /* options set on the command line */ |
84 | int err; | 131 | g_flags = (g_flags & ~SWAP_FLAG_DISCARD_MASK) | SWAP_FLAG_DISCARD; |
132 | |||
133 | if (!s) /* No optional policy value on the commandline */ | ||
134 | return; | ||
135 | /* Skip prepended '=' */ | ||
136 | if (*s == '=') | ||
137 | s++; | ||
138 | /* For fstab parsing: remove other appended options */ | ||
139 | *strchrnul(s, ',') = '\0'; | ||
140 | |||
141 | if (strcmp(s, "once") == 0) | ||
142 | g_flags |= SWAP_FLAG_DISCARD_ONCE; | ||
143 | if (strcmp(s, "pages") == 0) | ||
144 | g_flags |= SWAP_FLAG_DISCARD_PAGES; | ||
145 | } | ||
146 | #else | ||
147 | #define set_discard_flag(s) ((void)0) | ||
148 | #endif | ||
85 | 149 | ||
86 | f = setmntent("/etc/fstab", "r"); | 150 | #if ENABLE_FEATURE_SWAPON_PRI |
87 | if (f == NULL) | 151 | static void set_priority_flag(char *s) |
88 | bb_perror_msg_and_die("/etc/fstab"); | 152 | { |
153 | unsigned prio; | ||
154 | |||
155 | /* For fstab parsing: remove other appended options */ | ||
156 | *strchrnul(s, ',') = '\0'; | ||
157 | /* Max allowed 32767 (== SWAP_FLAG_PRIO_MASK) */ | ||
158 | prio = bb_strtou(s, NULL, 10); | ||
159 | if (!errno) { | ||
160 | /* Unset the flag first to allow fstab options to override */ | ||
161 | /* options set on the command line */ | ||
162 | g_flags = (g_flags & ~SWAP_FLAG_PRIO_MASK) | SWAP_FLAG_PREFER | | ||
163 | MIN(prio, SWAP_FLAG_PRIO_MASK); | ||
164 | } | ||
165 | } | ||
166 | #else | ||
167 | #define set_priority_flag(s) ((void)0) | ||
168 | #endif | ||
169 | |||
170 | static int do_em_all_in_fstab(void) | ||
171 | { | ||
172 | struct mntent *m; | ||
173 | int err = 0; | ||
174 | FILE *f = xfopen_for_read("/etc/fstab"); | ||
89 | 175 | ||
90 | err = 0; | ||
91 | while ((m = getmntent(f)) != NULL) { | 176 | while ((m = getmntent(f)) != NULL) { |
92 | if (strcmp(m->mnt_type, MNTTYPE_SWAP) == 0) { | 177 | if (strcmp(m->mnt_type, MNTTYPE_SWAP) == 0) { |
93 | /* swapon -a should ignore entries with noauto, | 178 | /* swapon -a should ignore entries with noauto, |
94 | * but swapoff -a should process them */ | 179 | * but swapoff -a should process them |
95 | if (applet_name[5] != 'n' | 180 | */ |
96 | || hasmntopt(m, MNTOPT_NOAUTO) == NULL | 181 | if (do_swapoff || hasmntopt(m, MNTOPT_NOAUTO) == NULL) { |
97 | ) { | 182 | /* each swap space might have different flags */ |
98 | #if ENABLE_FEATURE_SWAPON_PRI | 183 | /* save global flags for the next round */ |
99 | char *p; | 184 | save_g_flags(); |
100 | g_flags = 0; /* each swap space might have different flags */ | 185 | if (ENABLE_FEATURE_SWAPON_DISCARD) { |
101 | p = hasmntopt(m, "pri"); | 186 | char *p = hasmntopt(m, "discard"); |
102 | if (p) { | 187 | if (p) { |
103 | /* Max allowed 32767 (==SWAP_FLAG_PRIO_MASK) */ | 188 | /* move to '=' or to end of string */ |
104 | unsigned int swap_prio = MIN(bb_strtou(p + 4 , NULL, 10), SWAP_FLAG_PRIO_MASK); | 189 | p += 7; |
105 | /* We want to allow "NNNN,foo", thus errno == EINVAL is allowed too */ | 190 | set_discard_flag(p); |
106 | if (errno != ERANGE) { | ||
107 | g_flags = SWAP_FLAG_PREFER | | ||
108 | (swap_prio << SWAP_FLAG_PRIO_SHIFT); | ||
109 | } | 191 | } |
110 | } | 192 | } |
111 | #endif | 193 | if (ENABLE_FEATURE_SWAPON_PRI) { |
112 | err += swap_enable_disable(m->mnt_fsname); | 194 | char *p = hasmntopt(m, "pri"); |
195 | if (p) { | ||
196 | set_priority_flag(p + 4); | ||
197 | } | ||
198 | } | ||
199 | err |= swap_enable_disable(m->mnt_fsname); | ||
200 | restore_g_flags(); | ||
113 | } | 201 | } |
114 | } | 202 | } |
115 | } | 203 | } |
@@ -120,38 +208,68 @@ static int do_em_all(void) | |||
120 | return err; | 208 | return err; |
121 | } | 209 | } |
122 | 210 | ||
211 | static int do_all_in_proc_swaps(void) | ||
212 | { | ||
213 | char *line; | ||
214 | int err = 0; | ||
215 | FILE *f = fopen_for_read("/proc/swaps"); | ||
216 | /* Don't complain if missing */ | ||
217 | if (f) { | ||
218 | while ((line = xmalloc_fgetline(f)) != NULL) { | ||
219 | if (line[0] == '/') { | ||
220 | *strchrnul(line, ' ') = '\0'; | ||
221 | err |= swap_enable_disable(line); | ||
222 | } | ||
223 | free(line); | ||
224 | } | ||
225 | if (ENABLE_FEATURE_CLEAN_UP) | ||
226 | fclose(f); | ||
227 | } | ||
228 | |||
229 | return err; | ||
230 | } | ||
231 | |||
232 | #define OPTSTR_SWAPON "a" \ | ||
233 | IF_FEATURE_SWAPON_DISCARD("d::") \ | ||
234 | IF_FEATURE_SWAPON_PRI("p:") | ||
235 | |||
123 | int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 236 | int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
124 | int swap_on_off_main(int argc UNUSED_PARAM, char **argv) | 237 | int swap_on_off_main(int argc UNUSED_PARAM, char **argv) |
125 | { | 238 | { |
126 | int ret; | 239 | IF_FEATURE_SWAPON_PRI(char *prio;) |
240 | IF_FEATURE_SWAPON_DISCARD(char *discard = NULL;) | ||
241 | int ret = 0; | ||
127 | 242 | ||
128 | INIT_G(); | 243 | INIT_G(); |
129 | 244 | ||
130 | #if !ENABLE_FEATURE_SWAPON_PRI | 245 | getopt32(argv, do_swapoff ? "a" : OPTSTR_SWAPON |
131 | ret = getopt32(argv, "a"); | 246 | IF_FEATURE_SWAPON_DISCARD(, &discard) |
132 | #else | 247 | IF_FEATURE_SWAPON_PRI(, &prio) |
133 | if (applet_name[5] == 'n') | 248 | ); |
134 | opt_complementary = "p+"; | ||
135 | ret = getopt32(argv, (applet_name[5] == 'n') ? "ap:" : "a", &g_flags); | ||
136 | |||
137 | if (ret & 2) { // -p | ||
138 | g_flags = SWAP_FLAG_PREFER | | ||
139 | ((g_flags & SWAP_FLAG_PRIO_MASK) << SWAP_FLAG_PRIO_SHIFT); | ||
140 | ret &= 1; | ||
141 | } | ||
142 | #endif | ||
143 | |||
144 | if (ret /* & 1: not needed */) // -a | ||
145 | return do_em_all(); | ||
146 | 249 | ||
147 | argv += optind; | 250 | argv += optind; |
148 | if (!*argv) | ||
149 | bb_show_usage(); | ||
150 | 251 | ||
151 | /* ret = 0; redundant */ | 252 | if (OPT_DISCARD) { |
152 | do { | 253 | set_discard_flag(discard); |
153 | ret += swap_enable_disable(*argv); | 254 | } |
154 | } while (*++argv); | 255 | if (OPT_PRIO) { |
256 | set_priority_flag(prio); | ||
257 | } | ||
155 | 258 | ||
259 | if (OPT_ALL) { | ||
260 | /* swapoff -a does also /proc/swaps */ | ||
261 | if (do_swapoff) | ||
262 | ret = do_all_in_proc_swaps(); | ||
263 | ret |= do_em_all_in_fstab(); | ||
264 | } else if (!*argv) { | ||
265 | /* if not -a we need at least one arg */ | ||
266 | bb_show_usage(); | ||
267 | } | ||
268 | /* Unset -a now to allow for more messages in swap_enable_disable */ | ||
269 | option_mask32 = option_mask32 & ~OPT_a; | ||
270 | /* Now process devices on the commandline if any */ | ||
271 | while (*argv) { | ||
272 | ret |= swap_enable_disable(*argv++); | ||
273 | } | ||
156 | return ret; | 274 | return ret; |
157 | } | 275 | } |
diff --git a/win32/mingw.c b/win32/mingw.c index 501c886e0..044d5c50f 100644 --- a/win32/mingw.c +++ b/win32/mingw.c | |||
@@ -943,11 +943,11 @@ char *win32_execable_file(const char *p) | |||
943 | if ( (path=malloc(len+5)) != NULL ) { | 943 | if ( (path=malloc(len+5)) != NULL ) { |
944 | memcpy(path, p, len); | 944 | memcpy(path, p, len); |
945 | memcpy(path+len, ".exe", 5); | 945 | memcpy(path+len, ".exe", 5); |
946 | if (execable_file(path)) { | 946 | if (file_is_executable(path)) { |
947 | return path; | 947 | return path; |
948 | } | 948 | } |
949 | memcpy(path+len, ".com", 5); | 949 | memcpy(path+len, ".com", 5); |
950 | if (execable_file(path)) { | 950 | if (file_is_executable(path)) { |
951 | return path; | 951 | return path; |
952 | } | 952 | } |
953 | free(path); | 953 | free(path); |
diff --git a/win32/process.c b/win32/process.c index e2e81c793..462731a2d 100644 --- a/win32/process.c +++ b/win32/process.c | |||
@@ -238,7 +238,7 @@ mingw_spawn_interpreter(int mode, const char *prog, const char *const *argv, con | |||
238 | else { | 238 | else { |
239 | char *path = xstrdup(getenv("PATH")); | 239 | char *path = xstrdup(getenv("PATH")); |
240 | char *tmp = path; | 240 | char *tmp = path; |
241 | char *iprog = find_execable(interpr, &tmp); | 241 | char *iprog = find_executable(interpr, &tmp); |
242 | free(path); | 242 | free(path); |
243 | if (!iprog) { | 243 | if (!iprog) { |
244 | free(new_argv); | 244 | free(new_argv); |
@@ -273,9 +273,9 @@ mingw_spawn_1(int mode, const char *cmd, const char *const *argv, const char *co | |||
273 | return -1; | 273 | return -1; |
274 | } | 274 | } |
275 | 275 | ||
276 | /* exists_execable() does not return new file name */ | 276 | /* executable_exists() does not return new file name */ |
277 | tmp = path = xstrdup(path); | 277 | tmp = path = xstrdup(path); |
278 | prog = find_execable(cmd, &tmp); | 278 | prog = find_executable(cmd, &tmp); |
279 | free(path); | 279 | free(path); |
280 | if (!prog) { | 280 | if (!prog) { |
281 | errno = ENOENT; | 281 | errno = ENOENT; |