aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2014-05-06 20:41:10 +0100
committerRon Yorston <rmy@pobox.com>2014-05-06 20:41:10 +0100
commitd3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e (patch)
tree4b364ba4b6b9e96c2629fe382fef0248d76833dd
parent7905d97aeece18da362a5a1e066abff2d2e5c16b (diff)
parentd257608a8429b64e1a04c7cb6d99975eeb2c3955 (diff)
downloadbusybox-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
-rw-r--r--coreutils/test.c2
-rw-r--r--debianutils/which.c99
-rw-r--r--docs/ifupdown_design.txt2
-rw-r--r--docs/new-applet-HOWTO.txt104
-rw-r--r--editors/vi.c795
-rw-r--r--include/applets.src.h51
-rw-r--r--include/libbb.h7
-rw-r--r--include/platform.h16
-rw-r--r--libbb/Kbuild.src2
-rw-r--r--libbb/executable.c (renamed from libbb/execable.c)50
-rw-r--r--libbb/getpty.c2
-rw-r--r--libbb/obscure.c2
-rw-r--r--libbb/platform.c18
-rw-r--r--libbb/rtc.c52
-rw-r--r--libbb/verror_msg.c5
-rw-r--r--libbb/xreadlink.c11
-rw-r--r--loginutils/su.c1
-rw-r--r--miscutils/Config.src34
-rw-r--r--miscutils/Kbuild.src1
-rw-r--r--miscutils/adjtimex.c25
-rw-r--r--miscutils/crond.c332
-rw-r--r--miscutils/less.c38
-rw-r--r--modutils/insmod.c2
-rw-r--r--modutils/modprobe-small.c204
-rw-r--r--modutils/modprobe.c2
-rw-r--r--networking/Config.src8
-rw-r--r--networking/ifupdown.c4
-rw-r--r--networking/ntpd.c72
-rw-r--r--networking/wget.c5
-rw-r--r--scripts/Makefile.build3
-rwxr-xr-xscripts/gen_build_files.sh2
-rwxr-xr-xscripts/trylink4
-rw-r--r--shell/ash.c11
-rw-r--r--shell/ash_test/ash-misc/local1.right4
-rwxr-xr-xshell/ash_test/ash-misc/local1.tests11
-rw-r--r--shell/hush.c12
-rw-r--r--shell/random.c142
-rw-r--r--shell/random.h16
-rw-r--r--util-linux/Config.src9
-rw-r--r--util-linux/rtcwake.c55
-rw-r--r--util-linux/script.c7
-rw-r--r--util-linux/swaponoff.c248
-rw-r--r--win32/mingw.c4
-rw-r--r--win32/process.c6
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 @@
24int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 20int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
25int which_main(int argc UNUSED_PARAM, char **argv) 21int 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.
6Credits: 6Credits:
7Matt Kraai - initial writeup 7Matt Kraai - initial writeup
8Mark Whitley - the remix 8Mark Whitley - the remix
9Thomas Lundquist - Trying to keep it updated. 9Thomas Lundquist - trying to keep it updated
10 10
11When doing this you should consider using the latest git HEAD. 11When doing this you should consider using the latest git HEAD.
12This is a good thing if you plan to getting it committed into mainline. 12This is a good thing if you plan to getting it committed into mainline.
@@ -16,14 +16,14 @@ Initial Write
16 16
17First, write your applet. Be sure to include copyright information at the top, 17First, write your applet. Be sure to include copyright information at the top,
18such as who you stole the code from and so forth. Also include the mini-GPL 18such as who you stole the code from and so forth. Also include the mini-GPL
19boilerplate. Be sure to name the main function <applet>_main instead of main. 19boilerplate and Config.in/Kbuild/usage/applet.h snippets (more on that below
20And be sure to put it in <applet>.c. Usage does not have to be taken care of by 20in this document). Be sure to name the main function <applet>_main instead
21your applet. 21of main. And be sure to put it in <applet>.c. Make sure to #include "libbb.h"
22Make sure to #include "libbb.h" as the first include file in your applet. 22as the first include file in your applet.
23 23
24For a new applet mu, here is the code that would go in mu.c: 24For 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
44int mu_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 60int mu_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
45int mu_main(int argc, char **argv) 61int 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
93int function(char *a) 111int 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
100Add <function_name>.o in the right alphabetically sorted place 118Remember about the kbuild snippet.
101in libbb/Kbuild.src. You should look at the conditional part of
102libbb/Kbuild.src as well.
103 119
104You should also try to find a suitable place in include/libbb.h for 120You should also try to find a suitable place in include/libbb.h for
105the function declaration. If not, add it somewhere anyway, with or without 121the 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
109tunable and add it there if it is. 125tunable and add it there if it is.
110 126
111 127
128Kbuild/Config.in/usage/applets.h snippets in .c files
129-----------------------------------------------------
130
131The old way of adding new applets was to put all the information needed by the
132configuration and build system into appropriate files (namely: Kbuild.src and
133Config.src in new applet's directory) and to add the applet declaration and
134usage info text to include/applets.src.h and include/usage.src.h respectively.
135
136Since the scripts/gen_build_files.sh script had been introduced, the preferred
137way is to have all these declarations contained within the applet .c files.
138
139Every line intended to be processed by gen_build_files.sh should start as a
140comment without any preceding whitespaces and be followed by an appropriate
141keyword - kbuild, config, usage or applet - and a colon, just like shown in the
142first example above.
143
144
112Placement / Directory 145Placement / Directory
113--------------------- 146---------------------
114 147
115Find the appropriate directory for your new applet. 148Find the appropriate directory for your new applet.
116 149
117Make sure you find the appropriate places in the files, the applets are 150Add the kbuild snippet to the .c file:
118sorted alphabetically.
119 151
120Add the applet to Kbuild.src in the chosen directory: 152//kbuild:lib-$(CONFIG_MU) += mu.o
121 153
122lib-$(CONFIG_MU) += mu.o 154Add the config snippet to the .c file:
123 155
124Add the applet to Config.src in the chosen directory: 156//config:config MU
125 157//config: bool "MU"
126config 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
133Usage String(s) 163Usage String(s)
134--------------- 164---------------
135 165
136Next, add usage information for you applet to include/usage.src.h. 166Next, add usage information for your applet to the .c file.
137This should look like the following: 167This 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
148If your program supports flags, the flags should be mentioned on the first 177If your program supports flags, the flags should be mentioned on the first
149line (-[abcde]) and a detailed description of each flag should go in the 178line ([-abcde]) and a detailed description of each flag should go in the
150mu_full_usage section, one flag per line. (Numerous examples of this 179mu_full_usage section, one flag per line.
151currently exist in usage.src.h.)
152 180
153 181
154Header Files 182Header Files
155------------ 183------------
156 184
157Next, add an entry to include/applets.src.h. Be *sure* to keep the list 185Finally add the applet declaration snippet. Be sure to read the top of
158in alphabetical order, or else it will break the binary-search lookup 186applets.src.h before adding your applet - it contains important info
159algorithm in busybox.c and the Gods of BusyBox smite you. Yea, verily: 187on applet macros and conventions.
160
161Be 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
168The Grand Announcement 192The 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
419static int init_text_buffer(char *); // init from file or create new
420static void edit_file(char *); // edit one file 494static void edit_file(char *); // edit one file
421static void do_cmd(int); // execute a command 495static void do_cmd(int); // execute a command
422static int next_tabstop(int); 496static int next_tabstop(int);
@@ -437,10 +511,12 @@ static void dot_next(void); // move dot to next line B-o-l
437static void dot_prev(void); // move dot to prev line B-o-l 511static void dot_prev(void); // move dot to prev line B-o-l
438static void dot_scroll(int, int); // move the screen up or down 512static void dot_scroll(int, int); // move the screen up or down
439static void dot_skip_over_ws(void); // move dot pat WS 513static void dot_skip_over_ws(void); // move dot pat WS
440static void dot_delete(void); // delete the char at 'dot'
441static char *bound_dot(char *); // make sure text[0] <= P < "end" 514static char *bound_dot(char *); // make sure text[0] <= P < "end"
442static char *new_screen(int, int); // malloc virtual screen memory 515static char *new_screen(int, int); // malloc virtual screen memory
443static 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
519static 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[]!
446static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' 522static 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
448static int st_test(char *, int, int, char *); // helper for skip_thing() 524static int st_test(char *, int, int, char *); // helper for skip_thing()
449static char *skip_thing(char *, int, int, int); // skip some object 525static char *skip_thing(char *, int, int, int); // skip some object
450static char *find_pair(char *, char); // find matching pair () [] {} 526static char *find_pair(char *, char); // find matching pair () [] {}
451static 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
530static 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[]!
454static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole 533static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455static 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
537static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
456static void show_help(void); // display some help info 538static void show_help(void); // display some help info
457static void rawmode(void); // set "raw" mode on tty 539static void rawmode(void); // set "raw" mode on tty
458static void cookmode(void); // return to "cooked" mode on tty 540static void cookmode(void); // return to "cooked" mode on tty
@@ -460,7 +542,6 @@ static void cookmode(void); // return to "cooked" mode on tty
460static int mysleep(int); 542static int mysleep(int);
461static int readit(void); // read (maybe cursor) key from stdin 543static int readit(void); // read (maybe cursor) key from stdin
462static int get_one_char(void); // read 1 char from stdin 544static int get_one_char(void); // read 1 char from stdin
463static 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
496static char *get_one_address(char *, int *); // get colon addr, if present 577static char *get_one_address(char *, int *); // get colon addr, if present
497static char *get_address(char *, int *, int *); // get two colon addrs, if present 578static char *get_address(char *, int *, int *); // get two colon addrs, if present
498static void colon(char *); // execute the "colon" mode cmds
499#endif 579#endif
580static void colon(char *); // execute the "colon" mode cmds
500#if ENABLE_FEATURE_VI_USE_SIGNALS 581#if ENABLE_FEATURE_VI_USE_SIGNALS
501static void winch_sig(int); // catch window size changes 582static void winch_sig(int); // catch window size changes
502static void suspend_sig(int); // catch ctrl-Z 583static 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[]!
517static 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
601static 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
520static char *text_yank(char *, char *, int); // save copy of "p" into a register 604static char *text_yank(char *, char *, int); // save copy of "p" into a register
521static char what_reg(void); // what is letter of current YDreg 605static char what_reg(void); // what is letter of current YDreg
522static void check_context(char); // remember context for '' command 606static void check_context(char); // remember context for '' command
523#endif 607#endif
608#if ENABLE_FEATURE_VI_UNDO
609static void flush_undo_data(void);
610static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
611static void undo_pop(void); // Undo the last operation
612# if ENABLE_FEATURE_VI_UNDO_QUEUE
613static 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
525static void crash_dummy(); 623static void crash_dummy();
526static void crash_test(); 624static void crash_test();
527static int crashme = 0; 625static int crashme = 0;
528#endif 626#endif
529 627
530
531static void write1(const char *out) 628static 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)
618static int init_text_buffer(char *fn) 723static 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!
913static void colon(char *buf) 1019static 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
1366static void Hit_Return(void) 1542static 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 ----------------------------------
1573static void dot_left(void) 1749static 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
1579static void dot_right(void) 1756static 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
1585static void dot_begin(void) 1763static 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
1590static void dot_end(void) 1769static 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
1615static void dot_next(void) 1795static void dot_next(void)
1616{ 1796{
1797 undo_queue_commit();
1617 dot = next_line(dot); 1798 dot = next_line(dot);
1618} 1799}
1619 1800
1620static void dot_prev(void) 1801static 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
1656static void dot_delete(void) // delete the char at 'dot'
1657{
1658 text_hole_delete(dot, dot);
1659}
1660
1661static char *bound_dot(char *p) // make sure text[0] <= P < "end" 1839static 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
1807static char *char_insert(char *p, char c) // insert the char c at 'p' 1985static 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
2273static 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)
2285static 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
2384static 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
2448static 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[]
2090static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive 2500// "undo" value indicates if this operation should be undo-able
2501static 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//
2126static char *yank_delete(char *start, char *stop, int dist, int yf) 2555static 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[]!
2221static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p' 2650static 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
2484static 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[]!
2496static int file_insert(const char *fn, char *p, int update_ro_status) 2924static 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/*
10name - applet name as it is typed on command line 10name - applet name as it is typed on command line
11name2 - applet name, converted to C (ether-wake: name2 = ether_wake) 11help - applet name, converted to C (ether-wake: help = ether_wake)
12main - corresponding <applet>_main to call (bzcat: main = bunzip2) 12main - corresponding <applet>_main to call (bzcat: main = bunzip2)
13l - location to install link to: [/usr]/[s]bin 13l - location to install link to: [/usr]/[s]bin
14s - suid type: 14s - 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))
104IF_CLEAR(APPLET(clear, BB_DIR_USR_BIN, BB_SUID_DROP)) 104IF_CLEAR(APPLET(clear, BB_DIR_USR_BIN, BB_SUID_DROP))
105IF_COMM(APPLET(comm, BB_DIR_USR_BIN, BB_SUID_DROP)) 105IF_COMM(APPLET(comm, BB_DIR_USR_BIN, BB_SUID_DROP))
106IF_CP(APPLET_NOEXEC(cp, cp, BB_DIR_BIN, BB_SUID_DROP, cp)) 106IF_CP(APPLET_NOEXEC(cp, cp, BB_DIR_BIN, BB_SUID_DROP, cp))
107IF_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: */
109IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE)) 108IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE))
110IF_CRYPTPW(APPLET(cryptpw, BB_DIR_USR_BIN, BB_SUID_DROP)) 109IF_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))
391IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) 390IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes))
392IF_ZCIP(APPLET(zcip, BB_DIR_SBIN, BB_SUID_DROP)) 391IF_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
931int execable_file(const char *name) FAST_FUNC; 931int file_is_executable(const char *name) FAST_FUNC;
932char *find_execable(const char *filename, char **PATHp) FAST_FUNC; 932char *find_executable(const char *filename, char **PATHp) FAST_FUNC;
933int exists_execable(const char *filename) FAST_FUNC; 933int 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};
1092extern const char *msg_eol; 1092extern const char *msg_eol;
1093extern smallint syslog_level;
1093extern smallint logmode; 1094extern smallint logmode;
1094extern int die_sleep; 1095extern int die_sleep;
1095extern uint8_t xfunc_error_retval; 1096extern 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
566extern int usleep(unsigned) FAST_FUNC;
567#endif
568
557#ifndef HAVE_VASPRINTF 569#ifndef HAVE_VASPRINTF
558# include <stdarg.h> 570# include <stdarg.h>
559extern int vasprintf(char **string_ptr, const char *format, va_list p) FAST_FUNC; 571extern 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
30lib-y += default_error_retval.o 30lib-y += default_error_retval.o
31lib-y += device_open.o 31lib-y += device_open.o
32lib-y += dump.o 32lib-y += dump.o
33lib-y += execable.o 33lib-y += executable.o
34lib-y += fclose_nonstdin.o 34lib-y += fclose_nonstdin.o
35lib-y += fflush_stdout_and_exit.o 35lib-y += fflush_stdout_and_exit.o
36lib-y += fgets_str.o 36lib-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 */
16int FAST_FUNC execable_file(const char *name) 16int 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
35char* FAST_FUNC find_execable(const char *filename, char **PATHp) 35char* 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 */
71int FAST_FUNC exists_execable(const char *filename) 80int 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
21int 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
21int FAST_FUNC vasprintf(char **string_ptr, const char *format, va_list p) 39int 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 */
41static 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 */
36int FAST_FUNC rtc_xopen(const char **default_rtc, int flags) 63int 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
55void FAST_FUNC rtc_read_tm(struct tm *ptm, int fd) 87void 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
15smallint syslog_level = LOG_ERR;
16#endif
14smallint logmode = LOGMODE_STDIO; 17smallint logmode = LOGMODE_STDIO;
15const char *msg_eol = "\n"; 18const 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
109char* FAST_FUNC xmalloc_realpath(const char *path) 109char* 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
136config 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
149config 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
156config 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
163config 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
170config CRONTAB 136config 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
12lib-$(CONFIG_BEEP) += beep.o 12lib-$(CONFIG_BEEP) += beep.o
13lib-$(CONFIG_CHAT) += chat.o 13lib-$(CONFIG_CHAT) += chat.o
14lib-$(CONFIG_CHRT) += chrt.o 14lib-$(CONFIG_CHRT) += chrt.o
15lib-$(CONFIG_CROND) += crond.o
16lib-$(CONFIG_CRONTAB) += crontab.o 15lib-$(CONFIG_CRONTAB) += crontab.o
17lib-$(CONFIG_DC) += dc.o 16lib-$(CONFIG_DC) += dc.o
18lib-$(CONFIG_DEVFSD) += devfsd.o 17lib-$(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
100struct globals { 130struct 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 */
153static 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 */ 168static 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
127static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2))); 176static void log7(const char *msg, ...)
128static 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), */ 184static 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
159static const char DowAry[] ALIGN1 = 193static 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
164static const char MonAry[] ALIGN1 = 197static 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
169static void ParseField(char *user, char *ary, int modvalue, int off, 201static 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
452static void process_cron_update_file(void) 503static 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
522static void set_env_vars(struct passwd *pas) 572static 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
537static void change_user(struct passwd *pas) 590static 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
552static pid_t 603static pid_t
553fork_job(const char *user, int mailFd, 604fork_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
691static void start_one_job(const char *user, CronLine *line) 749static 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
904static 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
852int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 913int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
853int crond_main(int argc UNUSED_PARAM, char **argv) 914int 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 */
408static void read_lines(void) 411static 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 @@
22extern int init_module(void *module, unsigned long len, const char *options); 22extern int init_module(void *module, unsigned long len, const char *options);
23extern int delete_module(const char *module, unsigned flags); 23extern int delete_module(const char *module, unsigned flags);
24extern int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret); 24extern 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
149static 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 */
147static char* str_2_list(const char *str) 163static 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
252static int pathname_matches_modname(const char *pathname, const char *modname) 294static 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
424static module_info* find_alias(const char *alias) 465static 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)
524static void process_module(char *name, const char *cmdline_options) 564static 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
667config 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
667config PSCAN 675config 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
732static void 742static void
733add_peers(char *s) 743add_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) ;
255ifdef builtin-target 255ifdef builtin-target
256quiet_cmd_link_o_target = LD $@ 256quiet_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)
258cmd_link_o_target = $(if $(strip $(obj-y)),\ 259cmd_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
269if test "$CONFIG_FEATURE_INDIVIDUAL" = y; then 269if 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
305fi 307fi
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 @@
1A1:'A'
2A2:''
3A3:''
4A4:'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 @@
1a=A
2f() {
3 local a
4 # the above line unsets $a
5 echo "A2:'$a'"
6 unset a
7 echo "A3:'$a'"
8}
9echo "A1:'$a'"
10f
11echo "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
12uint32_t FAST_FUNC 35uint32_t FAST_FUNC
13next_random(random_t *rnd) 36next_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
143static random_t rnd;
144
145int 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 @@
12PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN 12PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
13 13
14typedef struct random_t { 14typedef 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
602config 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
602config FEATURE_SWAPON_PRI 611config 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
56static NOINLINE bool may_wakeup(const char *rtcname) 55static 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)
122int rtcwake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 121int rtcwake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
123int rtcwake_main(int argc UNUSED_PARAM, char **argv) 122int 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
42struct globals { 61struct 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 */
78enum {
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
52static int swap_enable_disable(char *device) 91static 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
80static int do_em_all(void) 126#if ENABLE_FEATURE_SWAPON_DISCARD
127static 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) 151static 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
170static 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
211static 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
123int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 236int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
124int swap_on_off_main(int argc UNUSED_PARAM, char **argv) 237int 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;