aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2014-06-30 21:13:06 +0100
committerRon Yorston <rmy@pobox.com>2014-06-30 21:13:06 +0100
commit099e8b6438345baae560a629d548af07a8c3125c (patch)
tree71b5600b22b0019af675e4a991394ce32c8207c5
parente19594cc6e49e78fa50a654f15cf9a04e77d054a (diff)
parent184b2669175e562d58894e22f6320cebf3316c25 (diff)
downloadbusybox-w32-099e8b6438345baae560a629d548af07a8c3125c.tar.gz
busybox-w32-099e8b6438345baae560a629d548af07a8c3125c.tar.bz2
busybox-w32-099e8b6438345baae560a629d548af07a8c3125c.zip
Merge branch 'busybox' into merge
-rw-r--r--Config.in8
-rw-r--r--Makefile.custom4
-rw-r--r--archival/cpio.c1
-rw-r--r--archival/libarchive/liblzo.h2
-rw-r--r--archival/libarchive/lzo1x_d.c3
-rw-r--r--coreutils/Config.src10
-rw-r--r--coreutils/cp.c1
-rw-r--r--coreutils/install.c14
-rw-r--r--coreutils/mkdir.c8
-rw-r--r--coreutils/mv.c8
-rw-r--r--coreutils/rm.c3
-rw-r--r--coreutils/rmdir.c10
-rw-r--r--coreutils/stat.c5
-rw-r--r--coreutils/unlink.c34
-rw-r--r--docs/unit-tests.txt50
-rw-r--r--editors/awk.c2
-rw-r--r--findutils/find.c159
-rw-r--r--findutils/xargs.c26
-rw-r--r--include/libbb.h146
-rw-r--r--include/platform.h3
-rw-r--r--libbb/Kbuild.src1
-rw-r--r--libbb/bbunit.c90
-rw-r--r--libbb/copy_file.c4
-rw-r--r--libbb/make_directory.c4
-rw-r--r--libbb/obscure.c38
-rw-r--r--libbb/remove_file.c8
-rw-r--r--libbb/strrstr.c19
-rw-r--r--libbb/sysconf.c22
-rw-r--r--loginutils/getty.c10
-rw-r--r--miscutils/nandwrite.c63
-rw-r--r--modutils/modprobe-small.c2
-rw-r--r--networking/ftpd.c77
-rw-r--r--networking/libiproute/iproute.c2
-rw-r--r--networking/route.c2
-rw-r--r--networking/udhcp/Config.src11
-rw-r--r--networking/udhcp/dhcpc.c4
-rw-r--r--procps/iostat.c7
-rw-r--r--procps/mpstat.c8
-rw-r--r--shell/ash.c2
-rw-r--r--sysklogd/syslogd.c30
-rwxr-xr-xtestsuite/awk.tests16
-rwxr-xr-xtestsuite/find.tests26
-rw-r--r--util-linux/fatattr.c104
43 files changed, 908 insertions, 139 deletions
diff --git a/Config.in b/Config.in
index dd4f6e86d..61e4088bf 100644
--- a/Config.in
+++ b/Config.in
@@ -700,6 +700,14 @@ config DEBUG_PESSIMIZE
700 in a much bigger executable that more closely matches the source 700 in a much bigger executable that more closely matches the source
701 code. 701 code.
702 702
703config UNIT_TEST
704 bool "Build unit tests"
705 default n
706 help
707 Say Y here if you want to build unit tests (both the framework and
708 test cases) as a Busybox applet. This results in bigger code, so you
709 probably don't want this option in production builds.
710
703config WERROR 711config WERROR
704 bool "Abort compilation on any warning" 712 bool "Abort compilation on any warning"
705 default n 713 default n
diff --git a/Makefile.custom b/Makefile.custom
index 8c95ef2d4..f8a12831d 100644
--- a/Makefile.custom
+++ b/Makefile.custom
@@ -55,7 +55,11 @@ endif
55# (cp -pPR is POSIX-compliant (cp -dpR or cp -a would not be)) 55# (cp -pPR is POSIX-compliant (cp -dpR or cp -a would not be))
56.PHONY: check 56.PHONY: check
57.PHONY: test 57.PHONY: test
58ifeq ($(CONFIG_UNIT_TEST),y)
59UNIT_CMD = ./busybox unit
60endif
58check test: busybox busybox.links 61check test: busybox busybox.links
62 $(UNIT_CMD)
59 test -d $(objtree)/testsuite || cp -pPR $(srctree)/testsuite $(objtree) 63 test -d $(objtree)/testsuite || cp -pPR $(srctree)/testsuite $(objtree)
60 bindir=$(objtree) srcdir=$(srctree)/testsuite \ 64 bindir=$(objtree) srcdir=$(srctree)/testsuite \
61 $(SHELL) -c "cd $(objtree)/testsuite && $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:0=),-v)" 65 $(SHELL) -c "cd $(objtree)/testsuite && $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:0=),-v)"
diff --git a/archival/cpio.c b/archival/cpio.c
index 1cce7c8b4..454648d68 100644
--- a/archival/cpio.c
+++ b/archival/cpio.c
@@ -65,6 +65,7 @@
65//usage: IF_FEATURE_CPIO_P( 65//usage: IF_FEATURE_CPIO_P(
66//usage: "\n -p DIR Copy files to DIR" 66//usage: "\n -p DIR Copy files to DIR"
67//usage: ) 67//usage: )
68//usage: "\nOptions:"
68//usage: "\n -d Make leading directories" 69//usage: "\n -d Make leading directories"
69//usage: "\n -m Preserve mtime" 70//usage: "\n -m Preserve mtime"
70//usage: "\n -v Verbose" 71//usage: "\n -v Verbose"
diff --git a/archival/libarchive/liblzo.h b/archival/libarchive/liblzo.h
index 843997cb9..4596620fe 100644
--- a/archival/libarchive/liblzo.h
+++ b/archival/libarchive/liblzo.h
@@ -76,11 +76,13 @@
76# define TEST_IP (ip < ip_end) 76# define TEST_IP (ip < ip_end)
77# define NEED_IP(x) \ 77# define NEED_IP(x) \
78 if ((unsigned)(ip_end - ip) < (unsigned)(x)) goto input_overrun 78 if ((unsigned)(ip_end - ip) < (unsigned)(x)) goto input_overrun
79# define TEST_IV(x) if ((x) > (unsigned)0 - (511)) goto input_overrun
79 80
80# undef TEST_OP /* don't need both of the tests here */ 81# undef TEST_OP /* don't need both of the tests here */
81# define TEST_OP 1 82# define TEST_OP 1
82# define NEED_OP(x) \ 83# define NEED_OP(x) \
83 if ((unsigned)(op_end - op) < (unsigned)(x)) goto output_overrun 84 if ((unsigned)(op_end - op) < (unsigned)(x)) goto output_overrun
85# define TEST_OV(x) if ((x) > (unsigned)0 - (511)) goto output_overrun
84 86
85#define HAVE_ANY_OP 1 87#define HAVE_ANY_OP 1
86 88
diff --git a/archival/libarchive/lzo1x_d.c b/archival/libarchive/lzo1x_d.c
index 9bc1270da..40b167e68 100644
--- a/archival/libarchive/lzo1x_d.c
+++ b/archival/libarchive/lzo1x_d.c
@@ -92,6 +92,7 @@ int lzo1x_decompress_safe(const uint8_t* in, unsigned in_len,
92 ip++; 92 ip++;
93 NEED_IP(1); 93 NEED_IP(1);
94 } 94 }
95 TEST_IV(t);
95 t += 15 + *ip++; 96 t += 15 + *ip++;
96 } 97 }
97 /* copy literals */ 98 /* copy literals */
@@ -224,6 +225,7 @@ int lzo1x_decompress_safe(const uint8_t* in, unsigned in_len,
224 ip++; 225 ip++;
225 NEED_IP(1); 226 NEED_IP(1);
226 } 227 }
228 TEST_IV(t);
227 t += 31 + *ip++; 229 t += 31 + *ip++;
228 } 230 }
229#if defined(COPY_DICT) 231#if defined(COPY_DICT)
@@ -265,6 +267,7 @@ int lzo1x_decompress_safe(const uint8_t* in, unsigned in_len,
265 ip++; 267 ip++;
266 NEED_IP(1); 268 NEED_IP(1);
267 } 269 }
270 TEST_IV(t);
268 t += 7 + *ip++; 271 t += 7 + *ip++;
269 } 272 }
270#if defined(COPY_DICT) 273#if defined(COPY_DICT)
diff --git a/coreutils/Config.src b/coreutils/Config.src
index 82b6bf0d9..2914fc36a 100644
--- a/coreutils/Config.src
+++ b/coreutils/Config.src
@@ -738,6 +738,16 @@ config YES
738 yes is used to repeatedly output a specific string, or 738 yes is used to repeatedly output a specific string, or
739 the default string `y'. 739 the default string `y'.
740 740
741comment "Common options"
742
743config FEATURE_VERBOSE
744 bool "Support verbose options (usually -v) for various applets"
745 default y
746 help
747 Enable cp -v, rm -v and similar messages.
748 Also enables long option (--verbose) if it exists.
749 Without this option, -v is accepted but ignored.
750
741comment "Common options for cp and mv" 751comment "Common options for cp and mv"
742 depends on CP || MV 752 depends on CP || MV
743 753
diff --git a/coreutils/cp.c b/coreutils/cp.c
index de2e512be..247ed0fda 100644
--- a/coreutils/cp.c
+++ b/coreutils/cp.c
@@ -79,7 +79,6 @@ int cp_main(int argc, char **argv)
79 "parents\0" No_argument "\xff" 79 "parents\0" No_argument "\xff"
80 ; 80 ;
81#endif 81#endif
82 // -v (--verbose) is ignored
83 flags = getopt32(argv, FILEUTILS_CP_OPTSTR "arPv"); 82 flags = getopt32(argv, FILEUTILS_CP_OPTSTR "arPv");
84 /* Options of cp from GNU coreutils 6.10: 83 /* Options of cp from GNU coreutils 6.10:
85 * -a, --archive 84 * -a, --archive
diff --git a/coreutils/install.c b/coreutils/install.c
index 445497f9a..6c88ae11c 100644
--- a/coreutils/install.c
+++ b/coreutils/install.c
@@ -28,6 +28,9 @@
28 28
29#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS 29#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS
30static const char install_longopts[] ALIGN1 = 30static const char install_longopts[] ALIGN1 =
31 IF_FEATURE_VERBOSE(
32 "verbose\0" No_argument "v"
33 )
31 "directory\0" No_argument "d" 34 "directory\0" No_argument "d"
32 "preserve-timestamps\0" No_argument "p" 35 "preserve-timestamps\0" No_argument "p"
33 "strip\0" No_argument "s" 36 "strip\0" No_argument "s"
@@ -89,6 +92,7 @@ int install_main(int argc, char **argv)
89 const char *gid_str; 92 const char *gid_str;
90 const char *uid_str; 93 const char *uid_str;
91 const char *mode_str; 94 const char *mode_str;
95 int mkdir_flags = FILEUTILS_RECUR;
92 int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE; 96 int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE;
93 int opts; 97 int opts;
94 int min_args = 1; 98 int min_args = 1;
@@ -120,7 +124,6 @@ int install_main(int argc, char **argv)
120#endif 124#endif
121 opt_complementary = "s--d:d--s" IF_FEATURE_INSTALL_LONG_OPTIONS(IF_SELINUX(":Z--\xff:\xff--Z")); 125 opt_complementary = "s--d:d--s" IF_FEATURE_INSTALL_LONG_OPTIONS(IF_SELINUX(":Z--\xff:\xff--Z"));
122 /* -c exists for backwards compatibility, it's needed */ 126 /* -c exists for backwards compatibility, it's needed */
123 /* -v is ignored ("print name of each created directory") */
124 /* -b is ignored ("make a backup of each existing destination file") */ 127 /* -b is ignored ("make a backup of each existing destination file") */
125 opts = getopt32(argv, "cvb" "Ddpsg:m:o:" IF_SELINUX("Z:"), 128 opts = getopt32(argv, "cvb" "Ddpsg:m:o:" IF_SELINUX("Z:"),
126 &gid_str, &mode_str, &uid_str IF_SELINUX(, &scontext)); 129 &gid_str, &mode_str, &uid_str IF_SELINUX(, &scontext));
@@ -141,6 +144,11 @@ int install_main(int argc, char **argv)
141 } 144 }
142#endif 145#endif
143 146
147 if ((opts & OPT_v) && FILEUTILS_VERBOSE) {
148 mkdir_flags |= FILEUTILS_VERBOSE;
149 copy_flags |= FILEUTILS_VERBOSE;
150 }
151
144 /* preserve access and modification time, this is GNU behaviour, 152 /* preserve access and modification time, this is GNU behaviour,
145 * BSD only preserves modification time */ 153 * BSD only preserves modification time */
146 if (opts & OPT_PRESERVE_TIME) { 154 if (opts & OPT_PRESERVE_TIME) {
@@ -171,14 +179,14 @@ int install_main(int argc, char **argv)
171 /* GNU coreutils 6.9 does not set uid:gid 179 /* GNU coreutils 6.9 does not set uid:gid
172 * on intermediate created directories 180 * on intermediate created directories
173 * (only on last one) */ 181 * (only on last one) */
174 if (bb_make_directory(dest, 0755, FILEUTILS_RECUR)) { 182 if (bb_make_directory(dest, 0755, mkdir_flags)) {
175 ret = EXIT_FAILURE; 183 ret = EXIT_FAILURE;
176 goto next; 184 goto next;
177 } 185 }
178 } else { 186 } else {
179 if (opts & OPT_MKDIR_LEADING) { 187 if (opts & OPT_MKDIR_LEADING) {
180 char *ddir = xstrdup(dest); 188 char *ddir = xstrdup(dest);
181 bb_make_directory(dirname(ddir), 0755, FILEUTILS_RECUR); 189 bb_make_directory(dirname(ddir), 0755, mkdir_flags);
182 /* errors are not checked. copy_file 190 /* errors are not checked. copy_file
183 * will fail if dir is not created. */ 191 * will fail if dir is not created. */
184 free(ddir); 192 free(ddir);
diff --git a/coreutils/mkdir.c b/coreutils/mkdir.c
index 4a8e43e43..864edfb0a 100644
--- a/coreutils/mkdir.c
+++ b/coreutils/mkdir.c
@@ -48,7 +48,9 @@ static const char mkdir_longopts[] ALIGN1 =
48#if ENABLE_SELINUX 48#if ENABLE_SELINUX
49 "context\0" Required_argument "Z" 49 "context\0" Required_argument "Z"
50#endif 50#endif
51#if ENABLE_FEATURE_VERBOSE
51 "verbose\0" No_argument "v" 52 "verbose\0" No_argument "v"
53#endif
52 ; 54 ;
53#endif 55#endif
54 56
@@ -67,7 +69,7 @@ int mkdir_main(int argc UNUSED_PARAM, char **argv)
67#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS 69#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS
68 applet_long_options = mkdir_longopts; 70 applet_long_options = mkdir_longopts;
69#endif 71#endif
70 opt = getopt32(argv, "m:p" IF_SELINUX("Z:") "v", &smode IF_SELINUX(,&scontext)); 72 opt = getopt32(argv, "m:pv" IF_SELINUX("Z:"), &smode IF_SELINUX(,&scontext));
71 if (opt & 1) { 73 if (opt & 1) {
72 mode_t mmode = 0777; 74 mode_t mmode = 0777;
73 if (!bb_parse_mode(smode, &mmode)) { 75 if (!bb_parse_mode(smode, &mmode)) {
@@ -77,8 +79,10 @@ int mkdir_main(int argc UNUSED_PARAM, char **argv)
77 } 79 }
78 if (opt & 2) 80 if (opt & 2)
79 flags |= FILEUTILS_RECUR; 81 flags |= FILEUTILS_RECUR;
82 if ((opt & 4) && FILEUTILS_VERBOSE)
83 flags |= FILEUTILS_VERBOSE;
80#if ENABLE_SELINUX 84#if ENABLE_SELINUX
81 if (opt & 4) { 85 if (opt & 8) {
82 selinux_or_die(); 86 selinux_or_die();
83 setfscreatecon_or_die(scontext); 87 setfscreatecon_or_die(scontext);
84 } 88 }
diff --git a/coreutils/mv.c b/coreutils/mv.c
index f127dfabd..50571755b 100644
--- a/coreutils/mv.c
+++ b/coreutils/mv.c
@@ -33,13 +33,17 @@ static const char mv_longopts[] ALIGN1 =
33 "interactive\0" No_argument "i" 33 "interactive\0" No_argument "i"
34 "force\0" No_argument "f" 34 "force\0" No_argument "f"
35 "no-clobber\0" No_argument "n" 35 "no-clobber\0" No_argument "n"
36 IF_FEATURE_VERBOSE(
36 "verbose\0" No_argument "v" 37 "verbose\0" No_argument "v"
38 )
37 ; 39 ;
38#endif 40#endif
39 41
40#define OPT_FORCE (1 << 0) 42#define OPT_FORCE (1 << 0)
41#define OPT_INTERACTIVE (1 << 1) 43#define OPT_INTERACTIVE (1 << 1)
42#define OPT_NOCLOBBER (1 << 2) 44#define OPT_NOCLOBBER (1 << 2)
45#define OPT_VERBOSE ((1 << 3) * ENABLE_FEATURE_VERBOSE)
46
43 47
44int mv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 48int mv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
45int mv_main(int argc, char **argv) 49int mv_main(int argc, char **argv)
@@ -58,7 +62,6 @@ int mv_main(int argc, char **argv)
58 /* Need at least two arguments. 62 /* Need at least two arguments.
59 * If more than one of -f, -i, -n is specified , only the final one 63 * If more than one of -f, -i, -n is specified , only the final one
60 * takes effect (it unsets previous options). 64 * takes effect (it unsets previous options).
61 * -v is accepted but ignored.
62 */ 65 */
63 opt_complementary = "-2:f-in:i-fn:n-fi"; 66 opt_complementary = "-2:f-in:i-fn:n-fi";
64 flags = getopt32(argv, "finv"); 67 flags = getopt32(argv, "finv");
@@ -148,6 +151,9 @@ int mv_main(int argc, char **argv)
148 status = 1; 151 status = 1;
149 } 152 }
150 RET_0: 153 RET_0:
154 if (flags & OPT_VERBOSE) {
155 printf("'%s' -> '%s'\n", *argv, dest);
156 }
151 if (dest != last) { 157 if (dest != last) {
152 free((void *) dest); 158 free((void *) dest);
153 } 159 }
diff --git a/coreutils/rm.c b/coreutils/rm.c
index 042fba162..d0ad81dfc 100644
--- a/coreutils/rm.c
+++ b/coreutils/rm.c
@@ -38,7 +38,6 @@ int rm_main(int argc UNUSED_PARAM, char **argv)
38 unsigned opt; 38 unsigned opt;
39 39
40 opt_complementary = "f-i:i-f"; 40 opt_complementary = "f-i:i-f";
41 /* -v (verbose) is ignored */
42 opt = getopt32(argv, "fiRrv"); 41 opt = getopt32(argv, "fiRrv");
43 argv += optind; 42 argv += optind;
44 if (opt & 1) 43 if (opt & 1)
@@ -47,6 +46,8 @@ int rm_main(int argc UNUSED_PARAM, char **argv)
47 flags |= FILEUTILS_INTERACTIVE; 46 flags |= FILEUTILS_INTERACTIVE;
48 if (opt & (8|4)) 47 if (opt & (8|4))
49 flags |= FILEUTILS_RECUR; 48 flags |= FILEUTILS_RECUR;
49 if ((opt & 16) && FILEUTILS_VERBOSE)
50 flags |= FILEUTILS_VERBOSE;
50 51
51 if (*argv != NULL) { 52 if (*argv != NULL) {
52 do { 53 do {
diff --git a/coreutils/rmdir.c b/coreutils/rmdir.c
index cc2dea010..0792a1c8e 100644
--- a/coreutils/rmdir.c
+++ b/coreutils/rmdir.c
@@ -31,7 +31,7 @@
31 31
32 32
33#define PARENTS (1 << 0) 33#define PARENTS (1 << 0)
34//efine VERBOSE (1 << 1) //accepted but ignored 34#define VERBOSE ((1 << 1) * ENABLE_FEATURE_VERBOSE)
35#define IGNORE_NON_EMPTY (1 << 2) 35#define IGNORE_NON_EMPTY (1 << 2)
36 36
37int rmdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 37int rmdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -44,10 +44,12 @@ int rmdir_main(int argc UNUSED_PARAM, char **argv)
44#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS 44#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS
45 static const char rmdir_longopts[] ALIGN1 = 45 static const char rmdir_longopts[] ALIGN1 =
46 "parents\0" No_argument "p" 46 "parents\0" No_argument "p"
47 "verbose\0" No_argument "v"
48 /* Debian etch: many packages fail to be purged or installed 47 /* Debian etch: many packages fail to be purged or installed
49 * because they desperately want this option: */ 48 * because they desperately want this option: */
50 "ignore-fail-on-non-empty\0" No_argument "\xff" 49 "ignore-fail-on-non-empty\0" No_argument "\xff"
50 IF_FEATURE_VERBOSE(
51 "verbose\0" No_argument "v"
52 )
51 ; 53 ;
52 applet_long_options = rmdir_longopts; 54 applet_long_options = rmdir_longopts;
53#endif 55#endif
@@ -62,6 +64,10 @@ int rmdir_main(int argc UNUSED_PARAM, char **argv)
62 path = *argv; 64 path = *argv;
63 65
64 while (1) { 66 while (1) {
67 if (flags & VERBOSE) {
68 printf("rmdir: removing directory, '%s'\n", path);
69 }
70
65 if (rmdir(path) < 0) { 71 if (rmdir(path) < 0) {
66#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS 72#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS
67 if ((flags & IGNORE_NON_EMPTY) && errno == ENOTEMPTY) 73 if ((flags & IGNORE_NON_EMPTY) && errno == ENOTEMPTY)
diff --git a/coreutils/stat.c b/coreutils/stat.c
index dc9d81c35..769fac078 100644
--- a/coreutils/stat.c
+++ b/coreutils/stat.c
@@ -655,7 +655,7 @@ static bool do_stat(const char *filename, const char *format)
655 ); 655 );
656# if ENABLE_SELINUX 656# if ENABLE_SELINUX
657 if (option_mask32 & OPT_SELINUX) 657 if (option_mask32 & OPT_SELINUX)
658 printf(" %lc\n", *scontext); 658 printf(" %s\n", scontext);
659 else 659 else
660 bb_putchar('\n'); 660 bb_putchar('\n');
661# endif 661# endif
@@ -700,7 +700,8 @@ static bool do_stat(const char *filename, const char *format)
700 (unsigned long) statbuf.st_gid, 700 (unsigned long) statbuf.st_gid,
701 (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN"); 701 (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN");
702# if ENABLE_SELINUX 702# if ENABLE_SELINUX
703 printf(" S_Context: %lc\n", *scontext); 703 if (option_mask32 & OPT_SELINUX)
704 printf(" S_Context: %s\n", scontext);
704# endif 705# endif
705 printf("Access: %s\n", human_time(statbuf.st_atime)); 706 printf("Access: %s\n", human_time(statbuf.st_atime));
706 printf("Modify: %s\n", human_time(statbuf.st_mtime)); 707 printf("Modify: %s\n", human_time(statbuf.st_mtime));
diff --git a/coreutils/unlink.c b/coreutils/unlink.c
new file mode 100644
index 000000000..3b7d0fb2b
--- /dev/null
+++ b/coreutils/unlink.c
@@ -0,0 +1,34 @@
1/* vi: set sw=4 ts=4: */
2/* unlink for busybox
3 *
4 * Copyright (C) 2014 Isaac Dunham <ibid.ag@gmail.com>
5 *
6 * Licensed under GPLv2, see LICENSE in this source tree
7 */
8
9//config:config UNLINK
10//config: bool "unlink"
11//config: default y
12//config: help
13//config: unlink deletes a file by calling unlink()
14
15//kbuild:lib-$(CONFIG_UNLINK) += unlink.o
16
17//applet:IF_UNLINK(APPLET(unlink, BB_DIR_USR_BIN, BB_SUID_DROP))
18
19//usage:#define unlink_trivial_usage
20//usage: "FILE"
21//usage:#define unlink_full_usage "\n\n"
22//usage: "Delete FILE by calling unlink()"
23
24#include "libbb.h"
25
26int unlink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
27int unlink_main(int argc UNUSED_PARAM, char **argv)
28{
29 opt_complementary = "=1"; /* must have exactly 1 param */
30 getopt32(argv, "");
31 argv += optind;
32 xunlink(argv[0]);
33 return 0;
34}
diff --git a/docs/unit-tests.txt b/docs/unit-tests.txt
new file mode 100644
index 000000000..0fb522086
--- /dev/null
+++ b/docs/unit-tests.txt
@@ -0,0 +1,50 @@
1Busybox unit test framework
2===========================
3
4This document describes what you need to do to write test cases using the
5Busybox unit test framework.
6
7
8Building unit tests
9-------------------
10
11The framework and all tests are built as a regular Busybox applet if option
12CONFIG_UNIT_TEST (found in General Configuration -> Debugging Options) is set.
13
14
15Writing test cases
16------------------
17
18Unit testing interface can be found in include/bbunit.h.
19
20Tests can be placed in any .c file in Busybox tree - preferably right next to
21the functions they test. Test cases should be enclosed within an #if, and
22should start with BBUNIT_DEFINE_TEST macro and end with BBUNIT_ENDTEST within
23the test curly brackets. If an assertion fails the test ends immediately, ie.
24the following assertions will not be reached. Any code placed after
25BBUNIT_ENDTEST is executed regardless of the test result. Here's an example:
26
27#if ENABLE_UNIT_TEST
28
29BBUNIT_DEFINE_TEST(test_name)
30{
31 int *i;
32
33 i = malloc(sizeof(int));
34 BBUNIT_ASSERT_NOTNULL(i);
35 *i = 2;
36 BBUNIT_ASSERT_EQ((*i)*(*i), 4);
37
38 BBUNIT_ENDTEST;
39
40 free(i);
41}
42
43#endif /* ENABLE_UNIT_TEST */
44
45
46Running the unit test suite
47---------------------------
48
49To run the tests you can either directly run 'busybox unit' or use 'make test'
50to run both the unit tests (if compiled) and regular test suite.
diff --git a/editors/awk.c b/editors/awk.c
index 3d2183e5e..5b8e484a7 100644
--- a/editors/awk.c
+++ b/editors/awk.c
@@ -1544,12 +1544,14 @@ static void chain_group(void)
1544 debug_printf_parse("%s: OC_BREAK\n", __func__); 1544 debug_printf_parse("%s: OC_BREAK\n", __func__);
1545 n = chain_node(OC_EXEC); 1545 n = chain_node(OC_EXEC);
1546 n->a.n = break_ptr; 1546 n->a.n = break_ptr;
1547 chain_expr(t_info);
1547 break; 1548 break;
1548 1549
1549 case OC_CONTINUE: 1550 case OC_CONTINUE:
1550 debug_printf_parse("%s: OC_CONTINUE\n", __func__); 1551 debug_printf_parse("%s: OC_CONTINUE\n", __func__);
1551 n = chain_node(OC_EXEC); 1552 n = chain_node(OC_EXEC);
1552 n->a.n = continue_ptr; 1553 n->a.n = continue_ptr;
1554 chain_expr(t_info);
1553 break; 1555 break;
1554 1556
1555 /* delete, next, nextfile, return, exit */ 1557 /* delete, next, nextfile, return, exit */
diff --git a/findutils/find.c b/findutils/find.c
index 6d34f4d68..56a7ed3ab 100644
--- a/findutils/find.c
+++ b/findutils/find.c
@@ -137,6 +137,16 @@
137//config: Support the 'find -exec' option for executing commands based upon 137//config: Support the 'find -exec' option for executing commands based upon
138//config: the files matched. 138//config: the files matched.
139//config: 139//config:
140//config:config FEATURE_FIND_EXEC_PLUS
141//config: bool "Enable -exec ... {} +"
142//config: default y
143//config: depends on FEATURE_FIND_EXEC
144//config: help
145//config: Support the 'find -exec ... {} +' option for executing commands
146//config: for all matched files at once.
147//config: Without this option, -exec + is a synonym for -exec ;
148//config: (IOW: it works correctly, but without expected speedup)
149//config:
140//config:config FEATURE_FIND_USER 150//config:config FEATURE_FIND_USER
141//config: bool "Enable -user: username/uid matching" 151//config: bool "Enable -user: username/uid matching"
142//config: default y 152//config: default y
@@ -319,6 +329,9 @@
319//usage: "\n -exec CMD ARG ; Run CMD with all instances of {} replaced by" 329//usage: "\n -exec CMD ARG ; Run CMD with all instances of {} replaced by"
320//usage: "\n file name. Fails if CMD exits with nonzero" 330//usage: "\n file name. Fails if CMD exits with nonzero"
321//usage: ) 331//usage: )
332//usage: IF_FEATURE_FIND_EXEC_PLUS(
333//usage: "\n -exec CMD ARG + Run CMD with {} replaced by list of file names"
334//usage: )
322//usage: IF_FEATURE_FIND_DELETE( 335//usage: IF_FEATURE_FIND_DELETE(
323//usage: "\n -delete Delete current file/directory. Turns on -depth option" 336//usage: "\n -delete Delete current file/directory. Turns on -depth option"
324//usage: ) 337//usage: )
@@ -337,8 +350,12 @@
337# define FNM_CASEFOLD 0 350# define FNM_CASEFOLD 0
338#endif 351#endif
339 352
340#define dbg(...) ((void)0) 353#if 1
341/* #define dbg(...) bb_error_msg(__VA_ARGS__) */ 354# define dbg(...) ((void)0)
355#else
356# define dbg(...) bb_error_msg(__VA_ARGS__)
357#endif
358
342 359
343/* This is a NOEXEC applet. Be very careful! */ 360/* This is a NOEXEC applet. Be very careful! */
344 361
@@ -375,7 +392,20 @@ IF_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;))
375IF_FEATURE_FIND_PAREN( ACTS(paren, action ***subexpr;)) 392IF_FEATURE_FIND_PAREN( ACTS(paren, action ***subexpr;))
376IF_FEATURE_FIND_PRUNE( ACTS(prune)) 393IF_FEATURE_FIND_PRUNE( ACTS(prune))
377IF_FEATURE_FIND_DELETE( ACTS(delete)) 394IF_FEATURE_FIND_DELETE( ACTS(delete))
378IF_FEATURE_FIND_EXEC( ACTS(exec, char **exec_argv; unsigned *subst_count; int exec_argc;)) 395IF_FEATURE_FIND_EXEC( ACTS(exec,
396 char **exec_argv; /* -exec ARGS */
397 unsigned *subst_count;
398 int exec_argc; /* count of ARGS */
399 IF_FEATURE_FIND_EXEC_PLUS(
400 /*
401 * filelist is NULL if "exec ;"
402 * non-NULL if "exec +"
403 */
404 char **filelist;
405 int filelist_idx;
406 int file_len;
407 )
408 ))
379IF_FEATURE_FIND_GROUP( ACTS(group, gid_t gid;)) 409IF_FEATURE_FIND_GROUP( ACTS(group, gid_t gid;))
380IF_FEATURE_FIND_LINKS( ACTS(links, char links_char; int links_count;)) 410IF_FEATURE_FIND_LINKS( ACTS(links, char links_char; int links_count;))
381 411
@@ -389,6 +419,7 @@ struct globals {
389 smallint need_print; 419 smallint need_print;
390 smallint xdev_on; 420 smallint xdev_on;
391 recurse_flags_t recurse_flags; 421 recurse_flags_t recurse_flags;
422 IF_FEATURE_FIND_EXEC_PLUS(unsigned max_argv_len;)
392} FIX_ALIASING; 423} FIX_ALIASING;
393#define G (*(struct globals*)&bb_common_bufsiz1) 424#define G (*(struct globals*)&bb_common_bufsiz1)
394#define INIT_G() do { \ 425#define INIT_G() do { \
@@ -398,6 +429,7 @@ struct globals {
398 /* we have to zero it out because of NOEXEC */ \ 429 /* we have to zero it out because of NOEXEC */ \
399 memset(&G, 0, sizeof(G)); \ 430 memset(&G, 0, sizeof(G)); \
400 IF_FEATURE_FIND_MAXDEPTH(G.minmaxdepth[1] = INT_MAX;) \ 431 IF_FEATURE_FIND_MAXDEPTH(G.minmaxdepth[1] = INT_MAX;) \
432 IF_FEATURE_FIND_EXEC_PLUS(G.max_argv_len = bb_arg_max() - 2048;) \
401 G.need_print = 1; \ 433 G.need_print = 1; \
402 G.recurse_flags = ACTION_RECURSE; \ 434 G.recurse_flags = ACTION_RECURSE; \
403} while (0) 435} while (0)
@@ -452,7 +484,6 @@ static int exec_actions(action ***appp, const char *fileName, const struct stat
452 return rc ^ TRUE; /* restore TRUE bit */ 484 return rc ^ TRUE; /* restore TRUE bit */
453} 485}
454 486
455
456#if !FNM_CASEFOLD 487#if !FNM_CASEFOLD
457static char *strcpy_upcase(char *dst, const char *src) 488static char *strcpy_upcase(char *dst, const char *src)
458{ 489{
@@ -576,17 +607,57 @@ ACTF(inum)
576} 607}
577#endif 608#endif
578#if ENABLE_FEATURE_FIND_EXEC 609#if ENABLE_FEATURE_FIND_EXEC
579ACTF(exec) 610static int do_exec(action_exec *ap, const char *fileName)
580{ 611{
581 int i, rc; 612 int i, rc;
582#if ENABLE_USE_PORTABLE_CODE 613# if ENABLE_FEATURE_FIND_EXEC_PLUS
583 char **argv = alloca(sizeof(char*) * (ap->exec_argc + 1)); 614 int size = ap->exec_argc + ap->filelist_idx + 1;
584#else /* gcc 4.3.1 generates smaller code: */ 615# else
585 char *argv[ap->exec_argc + 1]; 616 int size = ap->exec_argc + 1;
586#endif 617# endif
587 for (i = 0; i < ap->exec_argc; i++) 618# if ENABLE_USE_PORTABLE_CODE
588 argv[i] = xmalloc_substitute_string(ap->exec_argv[i], ap->subst_count[i], "{}", fileName); 619 char **argv = alloca(sizeof(char*) * size);
589 argv[i] = NULL; /* terminate the list */ 620# else /* gcc 4.3.1 generates smaller code: */
621 char *argv[size];
622# endif
623 char **pp = argv;
624
625 for (i = 0; i < ap->exec_argc; i++) {
626 const char *arg = ap->exec_argv[i];
627
628# if ENABLE_FEATURE_FIND_EXEC_PLUS
629 if (ap->filelist) {
630 /* Handling "-exec +"
631 * Only one exec_argv[i] has substitution in it.
632 * Expand that one exec_argv[i] into file list.
633 */
634 if (ap->subst_count[i] == 0) {
635 *pp++ = xstrdup(arg);
636 } else {
637 int j = 0;
638 while (ap->filelist[j]) {
639 /* 2nd arg here should be ap->subst_count[i], but it is always 1: */
640 *pp++ = xmalloc_substitute_string(arg, 1, "{}", ap->filelist[j]);
641 free(ap->filelist[j]);
642 j++;
643 }
644 }
645 } else
646# endif
647 {
648 /* Handling "-exec ;" */
649 *pp++ = xmalloc_substitute_string(arg, ap->subst_count[i], "{}", fileName);
650 }
651 }
652 *pp = NULL; /* terminate the list */
653
654# if ENABLE_FEATURE_FIND_EXEC_PLUS
655 if (ap->filelist) {
656 ap->filelist[0] = NULL;
657 ap->filelist_idx = 0;
658 ap->file_len = 0;
659 }
660# endif
590 661
591 rc = spawn_and_wait(argv); 662 rc = spawn_and_wait(argv);
592 if (rc < 0) 663 if (rc < 0)
@@ -597,6 +668,48 @@ ACTF(exec)
597 free(argv[i++]); 668 free(argv[i++]);
598 return rc == 0; /* return 1 if exitcode 0 */ 669 return rc == 0; /* return 1 if exitcode 0 */
599} 670}
671ACTF(exec)
672{
673# if ENABLE_FEATURE_FIND_EXEC_PLUS
674 if (ap->filelist) {
675 int rc;
676
677 ap->filelist = xrealloc_vector(ap->filelist, 8, ap->filelist_idx);
678 ap->filelist[ap->filelist_idx++] = xstrdup(fileName);
679 ap->file_len += strlen(fileName) + sizeof(char*) + 1;
680 /* If we have lots of files already, exec the command */
681 rc = 1;
682 if (ap->file_len >= G.max_argv_len)
683 rc = do_exec(ap, NULL);
684 return rc;
685 }
686# endif
687 return do_exec(ap, fileName);
688}
689# if ENABLE_FEATURE_FIND_EXEC_PLUS
690static int flush_exec_plus(void)
691{
692 action *ap;
693 action **app;
694 action ***appp = G.actions;
695 while ((app = *appp++) != NULL) {
696 while ((ap = *app++) != NULL) {
697 if (ap->f == (action_fp)func_exec) {
698 action_exec *ae = (void*)ap;
699 if (ae->filelist_idx != 0) {
700 int rc = do_exec(ae, NULL);
701# if ENABLE_FEATURE_FIND_NOT
702 if (ap->invert) rc = !rc;
703# endif
704 if (rc == 0)
705 return 1;
706 }
707 }
708 }
709 }
710 return 0;
711}
712# endif
600#endif 713#endif
601#if ENABLE_FEATURE_FIND_USER 714#if ENABLE_FEATURE_FIND_USER
602ACTF(user) 715ACTF(user)
@@ -1037,6 +1150,7 @@ static action*** parse_params(char **argv)
1037 else if (parm == PARM_exec) { 1150 else if (parm == PARM_exec) {
1038 int i; 1151 int i;
1039 action_exec *ap; 1152 action_exec *ap;
1153 IF_FEATURE_FIND_EXEC_PLUS(int all_subst = 0;)
1040 dbg("%d", __LINE__); 1154 dbg("%d", __LINE__);
1041 G.need_print = 0; 1155 G.need_print = 0;
1042 ap = ALLOC_ACTION(exec); 1156 ap = ALLOC_ACTION(exec);
@@ -1049,10 +1163,13 @@ static action*** parse_params(char **argv)
1049 // executes "echo Foo >FILENAME<", 1163 // executes "echo Foo >FILENAME<",
1050 // find -exec echo Foo ">{}<" "+" 1164 // find -exec echo Foo ">{}<" "+"
1051 // executes "echo Foo FILENAME1 FILENAME2 FILENAME3...". 1165 // executes "echo Foo FILENAME1 FILENAME2 FILENAME3...".
1052 // TODO (so far we treat "+" just like ";")
1053 if ((argv[0][0] == ';' || argv[0][0] == '+') 1166 if ((argv[0][0] == ';' || argv[0][0] == '+')
1054 && argv[0][1] == '\0' 1167 && argv[0][1] == '\0'
1055 ) { 1168 ) {
1169# if ENABLE_FEATURE_FIND_EXEC_PLUS
1170 if (argv[0][0] == '+')
1171 ap->filelist = xzalloc(sizeof(ap->filelist[0]));
1172# endif
1056 break; 1173 break;
1057 } 1174 }
1058 argv++; 1175 argv++;
@@ -1062,8 +1179,17 @@ static action*** parse_params(char **argv)
1062 bb_error_msg_and_die(bb_msg_requires_arg, arg); 1179 bb_error_msg_and_die(bb_msg_requires_arg, arg);
1063 ap->subst_count = xmalloc(ap->exec_argc * sizeof(int)); 1180 ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
1064 i = ap->exec_argc; 1181 i = ap->exec_argc;
1065 while (i--) 1182 while (i--) {
1066 ap->subst_count[i] = count_strstr(ap->exec_argv[i], "{}"); 1183 ap->subst_count[i] = count_strstr(ap->exec_argv[i], "{}");
1184 IF_FEATURE_FIND_EXEC_PLUS(all_subst += ap->subst_count[i];)
1185 }
1186# if ENABLE_FEATURE_FIND_EXEC_PLUS
1187 /*
1188 * coreutils expects {} to appear only once in "-exec +"
1189 */
1190 if (all_subst != 1 && ap->filelist)
1191 bb_error_msg_and_die("only one '{}' allowed for -exec +");
1192# endif
1067 } 1193 }
1068#endif 1194#endif
1069#if ENABLE_FEATURE_FIND_PAREN 1195#if ENABLE_FEATURE_FIND_PAREN
@@ -1335,8 +1461,11 @@ int find_main(int argc UNUSED_PARAM, char **argv)
1335 0) /* depth */ 1461 0) /* depth */
1336 ) { 1462 ) {
1337 status = EXIT_FAILURE; 1463 status = EXIT_FAILURE;
1464 goto out;
1338 } 1465 }
1339 } 1466 }
1340 1467
1468 IF_FEATURE_FIND_EXEC_PLUS(status = flush_exec_plus();)
1469out:
1341 return status; 1470 return status;
1342} 1471}
diff --git a/findutils/xargs.c b/findutils/xargs.c
index 0ba5b566d..76c4747fe 100644
--- a/findutils/xargs.c
+++ b/findutils/xargs.c
@@ -523,12 +523,7 @@ int xargs_main(int argc, char **argv)
523 argc++; 523 argc++;
524 } 524 }
525 525
526 /* -s NUM default. fileutils-4.4.2 uses 128k, but I heasitate 526 /*
527 * to use such a big value - first need to change code to use
528 * growable buffer instead of fixed one.
529 */
530 n_max_chars = 32 * 1024;
531 /* Make smaller if system does not allow our default value.
532 * The Open Group Base Specifications Issue 6: 527 * The Open Group Base Specifications Issue 6:
533 * "The xargs utility shall limit the command line length such that 528 * "The xargs utility shall limit the command line length such that
534 * when the command line is invoked, the combined argument 529 * when the command line is invoked, the combined argument
@@ -536,16 +531,15 @@ int xargs_main(int argc, char **argv)
536 * in the System Interfaces volume of IEEE Std 1003.1-2001) 531 * in the System Interfaces volume of IEEE Std 1003.1-2001)
537 * shall not exceed {ARG_MAX}-2048 bytes". 532 * shall not exceed {ARG_MAX}-2048 bytes".
538 */ 533 */
539 { 534 n_max_chars = bb_arg_max();
540 long arg_max = 0; 535 if (n_max_chars > 32 * 1024)
541#if defined _SC_ARG_MAX 536 n_max_chars = 32 * 1024;
542 arg_max = sysconf(_SC_ARG_MAX) - 2048; 537 /*
543#elif defined ARG_MAX 538 * POSIX suggests substracting 2048 bytes from sysconf(_SC_ARG_MAX)
544 arg_max = ARG_MAX - 2048; 539 * so that the process may safely modify its environment.
545#endif 540 */
546 if (arg_max > 0 && n_max_chars > arg_max) 541 n_max_chars -= 2048;
547 n_max_chars = arg_max; 542
548 }
549 if (opt & OPT_UPTO_SIZE) { 543 if (opt & OPT_UPTO_SIZE) {
550 n_max_chars = xatou_range(max_chars, 1, INT_MAX); 544 n_max_chars = xatou_range(max_chars, 1, INT_MAX);
551 } 545 }
diff --git a/include/libbb.h b/include/libbb.h
index c80cd80d7..f5ecc025f 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -347,6 +347,8 @@ enum { /* DO NOT CHANGE THESE VALUES! cp.c, mv.c, install.c depend on them. */
347 FILEUTILS_SET_SECURITY_CONTEXT = 1 << 10, 347 FILEUTILS_SET_SECURITY_CONTEXT = 1 << 10,
348#endif 348#endif
349 FILEUTILS_IGNORE_CHMOD_ERR = 1 << 11, 349 FILEUTILS_IGNORE_CHMOD_ERR = 1 << 11,
350 /* -v */
351 FILEUTILS_VERBOSE = (1 << 12) * ENABLE_FEATURE_VERBOSE,
350}; 352};
351#define FILEUTILS_CP_OPTSTR "pdRfilsLH" IF_SELINUX("c") 353#define FILEUTILS_CP_OPTSTR "pdRfilsLH" IF_SELINUX("c")
352extern int remove_file(const char *path, int flags) FAST_FUNC; 354extern int remove_file(const char *path, int flags) FAST_FUNC;
@@ -742,6 +744,15 @@ extern void *xmalloc_open_read_close(const char *filename, size_t *maxsz_p) FAST
742/* Never returns NULL */ 744/* Never returns NULL */
743extern void *xmalloc_xopen_read_close(const char *filename, size_t *maxsz_p) FAST_FUNC RETURNS_MALLOC; 745extern void *xmalloc_xopen_read_close(const char *filename, size_t *maxsz_p) FAST_FUNC RETURNS_MALLOC;
744 746
747#if defined ARG_MAX
748# define bb_arg_max() ((unsigned)ARG_MAX)
749#elif defined _SC_ARG_MAX
750unsigned bb_arg_max(void) FAST_FUNC;
751#else
752# define bb_arg_max() ((unsigned)(32 * 1024))
753#endif
754unsigned bb_clk_tck(void) FAST_FUNC;
755
745#define SEAMLESS_COMPRESSION (0 \ 756#define SEAMLESS_COMPRESSION (0 \
746 || ENABLE_FEATURE_SEAMLESS_XZ \ 757 || ENABLE_FEATURE_SEAMLESS_XZ \
747 || ENABLE_FEATURE_SEAMLESS_LZMA \ 758 || ENABLE_FEATURE_SEAMLESS_LZMA \
@@ -1957,6 +1968,141 @@ static ALWAYS_INLINE unsigned char bb_ascii_tolower(unsigned char a)
1957#define isprint_asciionly(a) ((unsigned)((a) - 0x20) <= 0x7e - 0x20) 1968#define isprint_asciionly(a) ((unsigned)((a) - 0x20) <= 0x7e - 0x20)
1958 1969
1959 1970
1971/* Simple unit-testing framework */
1972
1973typedef void (*bbunit_testfunc)(void);
1974
1975struct bbunit_listelem {
1976 struct bbunit_listelem* next;
1977 const char* name;
1978 bbunit_testfunc testfunc;
1979};
1980
1981void bbunit_registertest(struct bbunit_listelem* test);
1982void bbunit_settestfailed(void);
1983
1984#define BBUNIT_DEFINE_TEST(NAME) \
1985 static void bbunit_##NAME##_test(void); \
1986 static struct bbunit_listelem bbunit_##NAME##_elem = { \
1987 .name = #NAME, \
1988 .testfunc = bbunit_##NAME##_test, \
1989 }; \
1990 static void INIT_FUNC bbunit_##NAME##_register(void) \
1991 { \
1992 bbunit_registertest(&bbunit_##NAME##_elem); \
1993 } \
1994 static void bbunit_##NAME##_test(void)
1995
1996/*
1997 * Both 'goto bbunit_end' and 'break' are here only to get rid
1998 * of compiler warnings.
1999 */
2000#define BBUNIT_ENDTEST \
2001 do { \
2002 goto bbunit_end; \
2003 bbunit_end: \
2004 break; \
2005 } while (0)
2006
2007#define BBUNIT_PRINTASSERTFAIL \
2008 do { \
2009 bb_error_msg( \
2010 "[ERROR] Assertion failed in file %s, line %d", \
2011 __FILE__, __LINE__); \
2012 } while (0)
2013
2014#define BBUNIT_ASSERTION_FAILED \
2015 do { \
2016 bbunit_settestfailed(); \
2017 goto bbunit_end; \
2018 } while (0)
2019
2020/*
2021 * Assertions.
2022 * For now we only offer assertions which cause tests to fail
2023 * immediately. In the future 'expects' might be added too -
2024 * similar to those offered by the gtest framework.
2025 */
2026#define BBUNIT_ASSERT_EQ(EXPECTED, ACTUAL) \
2027 do { \
2028 if ((EXPECTED) != (ACTUAL)) { \
2029 BBUNIT_PRINTASSERTFAIL; \
2030 bb_error_msg("[ERROR] '%s' isn't equal to '%s'", \
2031 #EXPECTED, #ACTUAL); \
2032 BBUNIT_ASSERTION_FAILED; \
2033 } \
2034 } while (0)
2035
2036#define BBUNIT_ASSERT_NOTEQ(EXPECTED, ACTUAL) \
2037 do { \
2038 if ((EXPECTED) == (ACTUAL)) { \
2039 BBUNIT_PRINTASSERTFAIL; \
2040 bb_error_msg("[ERROR] '%s' is equal to '%s'", \
2041 #EXPECTED, #ACTUAL); \
2042 BBUNIT_ASSERTION_FAILED; \
2043 } \
2044 } while (0)
2045
2046#define BBUNIT_ASSERT_NOTNULL(PTR) \
2047 do { \
2048 if ((PTR) == NULL) { \
2049 BBUNIT_PRINTASSERTFAIL; \
2050 bb_error_msg("[ERROR] '%s' is NULL!", #PTR); \
2051 BBUNIT_ASSERTION_FAILED; \
2052 } \
2053 } while (0)
2054
2055#define BBUNIT_ASSERT_NULL(PTR) \
2056 do { \
2057 if ((PTR) != NULL) { \
2058 BBUNIT_PRINTASSERTFAIL; \
2059 bb_error_msg("[ERROR] '%s' is not NULL!", #PTR); \
2060 BBUNIT_ASSERTION_FAILED; \
2061 } \
2062 } while (0)
2063
2064#define BBUNIT_ASSERT_FALSE(STATEMENT) \
2065 do { \
2066 if ((STATEMENT)) { \
2067 BBUNIT_PRINTASSERTFAIL; \
2068 bb_error_msg("[ERROR] Statement '%s' evaluated to true!", \
2069 #STATEMENT); \
2070 BBUNIT_ASSERTION_FAILED; \
2071 } \
2072 } while (0)
2073
2074#define BBUNIT_ASSERT_TRUE(STATEMENT) \
2075 do { \
2076 if (!(STATEMENT)) { \
2077 BBUNIT_PRINTASSERTFAIL; \
2078 bb_error_msg("[ERROR] Statement '%s' evaluated to false!", \
2079 #STATEMENT); \
2080 BBUNIT_ASSERTION_FAILED; \
2081 } \
2082 } while (0)
2083
2084#define BBUNIT_ASSERT_STREQ(STR1, STR2) \
2085 do { \
2086 if (strcmp(STR1, STR2) != 0) { \
2087 BBUNIT_PRINTASSERTFAIL; \
2088 bb_error_msg("[ERROR] Strings '%s' and '%s' " \
2089 "are not the same", STR1, STR2); \
2090 BBUNIT_ASSERTION_FAILED; \
2091 } \
2092 } while (0)
2093
2094#define BBUNIT_ASSERT_STRNOTEQ(STR1, STR2) \
2095 do { \
2096 if (strcmp(STR1, STR2) == 0) { \
2097 BBUNIT_PRINTASSERTFAIL; \
2098 bb_error_msg("[ERROR] Strings '%s' and '%s' " \
2099 "are the same, but were " \
2100 "expected to differ", STR1, STR2); \
2101 BBUNIT_ASSERTION_FAILED; \
2102 } \
2103 } while (0)
2104
2105
1960POP_SAVED_FUNCTION_VISIBILITY 2106POP_SAVED_FUNCTION_VISIBILITY
1961 2107
1962#endif 2108#endif
diff --git a/include/platform.h b/include/platform.h
index 884c6e95f..d9b82b2ed 100644
--- a/include/platform.h
+++ b/include/platform.h
@@ -85,6 +85,9 @@
85# define UNUSED_PARAM_RESULT 85# define UNUSED_PARAM_RESULT
86#endif 86#endif
87 87
88/* used by unit test machinery to run registration functions before calling main() */
89#define INIT_FUNC __attribute__ ((constructor))
90
88/* -fwhole-program makes all symbols local. The attribute externally_visible 91/* -fwhole-program makes all symbols local. The attribute externally_visible
89 * forces a symbol global. */ 92 * forces a symbol global. */
90#if __GNUC_PREREQ(4,1) 93#if __GNUC_PREREQ(4,1)
diff --git a/libbb/Kbuild.src b/libbb/Kbuild.src
index 107c80689..d7ab1b129 100644
--- a/libbb/Kbuild.src
+++ b/libbb/Kbuild.src
@@ -84,6 +84,7 @@ lib-y += skip_whitespace.o
84lib-y += speed_table.o 84lib-y += speed_table.o
85lib-y += str_tolower.o 85lib-y += str_tolower.o
86lib-y += strrstr.o 86lib-y += strrstr.o
87lib-y += sysconf.o
87lib-y += time.o 88lib-y += time.o
88lib-y += trim.o 89lib-y += trim.o
89lib-y += u_signal_names.o 90lib-y += u_signal_names.o
diff --git a/libbb/bbunit.c b/libbb/bbunit.c
new file mode 100644
index 000000000..256014441
--- /dev/null
+++ b/libbb/bbunit.c
@@ -0,0 +1,90 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * bbunit: Simple unit-testing framework for Busybox.
4 *
5 * Copyright (C) 2014 by Bartosz Golaszewski <bartekgola@gmail.com>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 */
9
10//kbuild:lib-$(CONFIG_UNIT_TEST) += bbunit.o
11//applet:IF_UNIT_TEST(APPLET(unit, BB_DIR_USR_BIN, BB_SUID_DROP))
12
13//usage:#define unit_trivial_usage
14//usage: ""
15//usage:#define unit_full_usage "\n\n"
16//usage: "Run the unit-test suite"
17
18#include "libbb.h"
19
20#define WANT_TIMING 0
21
22static llist_t *tests = NULL;
23static unsigned tests_registered = 0;
24static int test_retval;
25
26void bbunit_registertest(struct bbunit_listelem *test)
27{
28 llist_add_to_end(&tests, test);
29 tests_registered++;
30}
31
32void bbunit_settestfailed(void)
33{
34 test_retval = -1;
35}
36
37#if WANT_TIMING
38static void timeval_diff(struct timeval* res,
39 const struct timeval* x,
40 const struct timeval* y)
41{
42 long udiff = x->tv_usec - y->tv_usec;
43
44 res->tv_sec = x->tv_sec - y->tv_sec - (udiff < 0);
45 res->tv_usec = (udiff >= 0 ? udiff : udiff + 1000000);
46}
47#endif
48
49int unit_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) MAIN_EXTERNALLY_VISIBLE;
50int unit_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
51{
52 unsigned tests_run = 0;
53 unsigned tests_failed = 0;
54#if WANT_TIMING
55 struct timeval begin;
56 struct timeval end;
57 struct timeval time_spent;
58 gettimeofday(&begin, NULL);
59#endif
60
61 bb_error_msg("Running %d test(s)...", tests_registered);
62 for (;;) {
63 struct bbunit_listelem* el = llist_pop(&tests);
64 if (!el)
65 break;
66 bb_error_msg("Case: [%s]", el->name);
67 test_retval = 0;
68 el->testfunc();
69 if (test_retval < 0) {
70 bb_error_msg("[ERROR] [%s]: TEST FAILED", el->name);
71 tests_failed++;
72 }
73 tests_run++;
74 el = el->next;
75 }
76
77#if WANT_TIMING
78 gettimeofday(&end, NULL);
79 timeval_diff(&time_spent, &end, &begin);
80 bb_error_msg("Elapsed time %u.%06u seconds"
81 (int)time_spent.tv_sec,
82 (int)time_spent.tv_usec);
83#endif
84 if (tests_failed > 0) {
85 bb_error_msg("[ERROR] %u test(s) FAILED", tests_failed);
86 return EXIT_FAILURE;
87 }
88 bb_error_msg("All tests passed");
89 return EXIT_SUCCESS;
90}
diff --git a/libbb/copy_file.c b/libbb/copy_file.c
index be65c4b47..935a9a4c4 100644
--- a/libbb/copy_file.c
+++ b/libbb/copy_file.c
@@ -398,5 +398,9 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
398 bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); 398 bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
399 } 399 }
400 400
401 if (flags & FILEUTILS_VERBOSE) {
402 printf("'%s' -> '%s'\n", source, dest);
403 }
404
401 return retval; 405 return retval;
402} 406}
diff --git a/libbb/make_directory.c b/libbb/make_directory.c
index 1f874f1f2..3980376ec 100644
--- a/libbb/make_directory.c
+++ b/libbb/make_directory.c
@@ -124,6 +124,10 @@ int FAST_FUNC bb_make_directory(char *path, long mode, int flags)
124 if (!c) { 124 if (!c) {
125 goto ret0; 125 goto ret0;
126 } 126 }
127 } else {
128 if (flags & FILEUTILS_VERBOSE) {
129 printf("created directory: '%s'\n", path);
130 }
127 } 131 }
128 132
129 if (!c) { 133 if (!c) {
diff --git a/libbb/obscure.c b/libbb/obscure.c
index 24c4ac917..ad17d1ff1 100644
--- a/libbb/obscure.c
+++ b/libbb/obscure.c
@@ -182,3 +182,41 @@ int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *
182 } 182 }
183 return 0; 183 return 0;
184} 184}
185
186#if ENABLE_UNIT_TEST
187
188/* Test obscure_msg() instead of obscure() in order not to print anything. */
189
190static const struct passwd pw = {
191 .pw_name = (char *)"johndoe",
192 .pw_gecos = (char *)"John Doe",
193};
194
195BBUNIT_DEFINE_TEST(obscure_weak_pass)
196{
197 /* Empty password */
198 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "", &pw));
199 /* Pure numbers */
200 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "23577315", &pw));
201 /* Similar to pw_name */
202 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "johndoe123%", &pw));
203 /* Similar to pw_gecos, reversed */
204 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "eoD nhoJ^44@", &pw));
205 /* Similar to the old password */
206 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "d4#21?'S", &pw));
207 /* adjacent letters */
208 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "qwerty123", &pw));
209 /* Many similar chars */
210 BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "^33Daaaaaa1", &pw));
211
212 BBUNIT_ENDTEST;
213}
214
215BBUNIT_DEFINE_TEST(obscure_strong_pass)
216{
217 BBUNIT_ASSERT_NULL(obscure_msg("Rt4##2&:'|", "}(^#rrSX3S*22", &pw));
218
219 BBUNIT_ENDTEST;
220}
221
222#endif /* ENABLE_UNIT_TEST */
diff --git a/libbb/remove_file.c b/libbb/remove_file.c
index 5b75f7f30..eaca293d9 100644
--- a/libbb/remove_file.c
+++ b/libbb/remove_file.c
@@ -78,6 +78,10 @@ int FAST_FUNC remove_file(const char *path, int flags)
78 return -1; 78 return -1;
79 } 79 }
80 80
81 if (flags & FILEUTILS_VERBOSE) {
82 printf("removed directory: '%s'\n", path);
83 }
84
81 return status; 85 return status;
82 } 86 }
83 87
@@ -98,5 +102,9 @@ int FAST_FUNC remove_file(const char *path, int flags)
98 return -1; 102 return -1;
99 } 103 }
100 104
105 if (flags & FILEUTILS_VERBOSE) {
106 printf("removed '%s'\n", path);
107 }
108
101 return 0; 109 return 0;
102} 110}
diff --git a/libbb/strrstr.c b/libbb/strrstr.c
index d8823fc51..93d970a1b 100644
--- a/libbb/strrstr.c
+++ b/libbb/strrstr.c
@@ -7,13 +7,7 @@
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 */ 8 */
9 9
10#ifdef __DO_STRRSTR_TEST
11#include <stdlib.h>
12#include <string.h>
13#include <stdio.h>
14#else
15#include "libbb.h" 10#include "libbb.h"
16#endif
17 11
18/* 12/*
19 * The strrstr() function finds the last occurrence of the substring needle 13 * The strrstr() function finds the last occurrence of the substring needle
@@ -34,8 +28,9 @@ char* FAST_FUNC strrstr(const char *haystack, const char *needle)
34 } 28 }
35} 29}
36 30
37#ifdef __DO_STRRSTR_TEST 31#if ENABLE_UNIT_TEST
38int main(int argc, char **argv) 32
33BBUNIT_DEFINE_TEST(strrstr)
39{ 34{
40 static const struct { 35 static const struct {
41 const char *h, *n; 36 const char *h, *n;
@@ -59,13 +54,13 @@ int main(int argc, char **argv)
59 i = 0; 54 i = 0;
60 while (i < sizeof(test_array) / sizeof(test_array[0])) { 55 while (i < sizeof(test_array) / sizeof(test_array[0])) {
61 const char *r = strrstr(test_array[i].h, test_array[i].n); 56 const char *r = strrstr(test_array[i].h, test_array[i].n);
62 printf("'%s' vs. '%s': '%s' - ", test_array[i].h, test_array[i].n, r);
63 if (r == NULL) 57 if (r == NULL)
64 r = test_array[i].h - 1; 58 r = test_array[i].h - 1;
65 printf("%s\n", r == test_array[i].h + test_array[i].pos ? "PASSED" : "FAILED"); 59 BBUNIT_ASSERT_EQ(r, test_array[i].h + test_array[i].pos);
66 i++; 60 i++;
67 } 61 }
68 62
69 return 0; 63 BBUNIT_ENDTEST;
70} 64}
71#endif 65
66#endif /* ENABLE_UNIT_TEST */
diff --git a/libbb/sysconf.c b/libbb/sysconf.c
new file mode 100644
index 000000000..031901980
--- /dev/null
+++ b/libbb/sysconf.c
@@ -0,0 +1,22 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * Various system configuration helpers.
4 *
5 * Copyright (C) 2014 Bartosz Golaszewski <bartekgola@gmail.com>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 */
9#include "libbb.h"
10
11#if !defined(ARG_MAX) && defined(_SC_ARG_MAX)
12unsigned FAST_FUNC bb_arg_max(void)
13{
14 return sysconf(_SC_ARG_MAX);
15}
16#endif
17
18/* Return the number of clock ticks per second. */
19unsigned FAST_FUNC bb_clk_tck(void)
20{
21 return sysconf(_SC_CLK_TCK);
22}
diff --git a/loginutils/getty.c b/loginutils/getty.c
index 0f060ae6c..4b1b73bef 100644
--- a/loginutils/getty.c
+++ b/loginutils/getty.c
@@ -556,6 +556,16 @@ int getty_main(int argc UNUSED_PARAM, char **argv)
556 // pid, getppid(), 556 // pid, getppid(),
557 // getsid(0), getpgid(0)); 557 // getsid(0), getpgid(0));
558 bb_perror_msg_and_die("setsid"); 558 bb_perror_msg_and_die("setsid");
559 /*
560 * When we can end up here?
561 * Example: setsid() fails when run alone in interactive shell:
562 * # getty 115200 /dev/tty2
563 * because shell's child (getty) is put in a new process group.
564 * But doesn't fail if shell is not interactive
565 * (and therefore doesn't create process groups for pipes),
566 * or if getty is not the first process in the process group:
567 * # true | getty 115200 /dev/tty2
568 */
559 } 569 }
560 /* Looks like we are already a session leader. 570 /* Looks like we are already a session leader.
561 * In this case (setsid failed) we may still have ctty, 571 * In this case (setsid failed) we may still have ctty,
diff --git a/miscutils/nandwrite.c b/miscutils/nandwrite.c
index e3f9b565d..29ff351a3 100644
--- a/miscutils/nandwrite.c
+++ b/miscutils/nandwrite.c
@@ -36,14 +36,16 @@
36//usage: "\n -s ADDR Start address" 36//usage: "\n -s ADDR Start address"
37 37
38//usage:#define nanddump_trivial_usage 38//usage:#define nanddump_trivial_usage
39//usage: "[-o] [-b] [-s ADDR] [-l LEN] [-f FILE] MTD_DEVICE" 39//usage: "[-o] [--bb=padbad|skipbad] [-s ADDR] [-l LEN] [-f FILE] MTD_DEVICE"
40//usage:#define nanddump_full_usage "\n\n" 40//usage:#define nanddump_full_usage "\n\n"
41//usage: "Dump MTD_DEVICE\n" 41//usage: "Dump MTD_DEVICE\n"
42//usage: "\n -o Dump oob data" 42//usage: "\n -o Dump oob data"
43//usage: "\n -b Omit bad block from the dump"
44//usage: "\n -s ADDR Start address" 43//usage: "\n -s ADDR Start address"
45//usage: "\n -l LEN Length" 44//usage: "\n -l LEN Length"
46//usage: "\n -f FILE Dump to file ('-' for stdout)" 45//usage: "\n -f FILE Dump to file ('-' for stdout)"
46//usage: "\n --bb=METHOD:"
47//usage: "\n skipbad: skip bad blocks"
48//usage: "\n padbad: substitute bad blocks by 0xff (default)"
47 49
48#include "libbb.h" 50#include "libbb.h"
49#include <mtd/mtd-user.h> 51#include <mtd/mtd-user.h>
@@ -54,9 +56,12 @@
54#define OPT_p (1 << 0) /* nandwrite only */ 56#define OPT_p (1 << 0) /* nandwrite only */
55#define OPT_o (1 << 0) /* nanddump only */ 57#define OPT_o (1 << 0) /* nanddump only */
56#define OPT_s (1 << 1) 58#define OPT_s (1 << 1)
57#define OPT_b (1 << 2) 59#define OPT_f (1 << 2)
58#define OPT_f (1 << 3) 60#define OPT_l (1 << 3)
59#define OPT_l (1 << 4) 61#define OPT_bb (1 << 4) /* must be the last one in the list */
62
63#define BB_PADBAD (1 << 0)
64#define BB_SKIPBAD (1 << 1)
60 65
61/* helper for writing out 0xff for bad blocks pad */ 66/* helper for writing out 0xff for bad blocks pad */
62static void dump_bad(struct mtd_info_user *meminfo, unsigned len, int oob) 67static void dump_bad(struct mtd_info_user *meminfo, unsigned len, int oob)
@@ -64,8 +69,8 @@ static void dump_bad(struct mtd_info_user *meminfo, unsigned len, int oob)
64 unsigned char buf[meminfo->writesize]; 69 unsigned char buf[meminfo->writesize];
65 unsigned count; 70 unsigned count;
66 71
67 /* round len to the next page */ 72 /* round len to the next page only if len is not already on a page */
68 len = (len | ~(meminfo->writesize - 1)) + 1; 73 len = ((len - 1) | (meminfo->writesize - 1)) + 1;
69 74
70 memset(buf, 0xff, sizeof(buf)); 75 memset(buf, 0xff, sizeof(buf));
71 for (count = 0; count < len; count += meminfo->writesize) { 76 for (count = 0; count < len; count += meminfo->writesize) {
@@ -102,6 +107,7 @@ int nandwrite_main(int argc UNUSED_PARAM, char **argv)
102 /* Buffer for OOB data */ 107 /* Buffer for OOB data */
103 unsigned char *oobbuf; 108 unsigned char *oobbuf;
104 unsigned opts; 109 unsigned opts;
110 unsigned bb_method = BB_SKIPBAD;
105 int fd; 111 int fd;
106 ssize_t cnt; 112 ssize_t cnt;
107 unsigned mtdoffset, meminfo_writesize, blockstart, limit; 113 unsigned mtdoffset, meminfo_writesize, blockstart, limit;
@@ -109,11 +115,14 @@ int nandwrite_main(int argc UNUSED_PARAM, char **argv)
109 struct mtd_info_user meminfo; 115 struct mtd_info_user meminfo;
110 struct mtd_oob_buf oob; 116 struct mtd_oob_buf oob;
111 unsigned char *filebuf; 117 unsigned char *filebuf;
112 const char *opt_s = "0", *opt_f = "-", *opt_l; 118 const char *opt_s = "0", *opt_f = "-", *opt_l, *opt_bb;
119 static const char nanddump_longopts[] ALIGN1 =
120 "bb\0" Required_argument "\xff"; /* no short equivalent */
113 121
114 if (IS_NANDDUMP) { 122 if (IS_NANDDUMP) {
115 opt_complementary = "=1"; 123 opt_complementary = "=1";
116 opts = getopt32(argv, "os:bf:l:", &opt_s, &opt_f, &opt_l); 124 applet_long_options = nanddump_longopts;
125 opts = getopt32(argv, "os:f:l:", &opt_s, &opt_f, &opt_l, &opt_bb);
117 } else { /* nandwrite */ 126 } else { /* nandwrite */
118 opt_complementary = "-1:?2"; 127 opt_complementary = "-1:?2";
119 opts = getopt32(argv, "ps:", &opt_s); 128 opts = getopt32(argv, "ps:", &opt_s);
@@ -138,6 +147,14 @@ int nandwrite_main(int argc UNUSED_PARAM, char **argv)
138 if (length < meminfo.size - mtdoffset) 147 if (length < meminfo.size - mtdoffset)
139 end_addr = mtdoffset + length; 148 end_addr = mtdoffset + length;
140 } 149 }
150 if (IS_NANDDUMP && (opts & OPT_bb)) {
151 if (strcmp("skipbad", opt_bb) == 0)
152 bb_method = BB_SKIPBAD;
153 else if (strcmp("padbad", opt_bb) == 0)
154 bb_method = BB_PADBAD;
155 else
156 bb_show_usage();
157 }
141 158
142 /* Pull it into a CPU register (hopefully) - smaller code that way */ 159 /* Pull it into a CPU register (hopefully) - smaller code that way */
143 meminfo_writesize = meminfo.writesize; 160 meminfo_writesize = meminfo.writesize;
@@ -162,9 +179,15 @@ int nandwrite_main(int argc UNUSED_PARAM, char **argv)
162 tmp = next_good_eraseblock(fd, &meminfo, blockstart); 179 tmp = next_good_eraseblock(fd, &meminfo, blockstart);
163 if (tmp != blockstart) { 180 if (tmp != blockstart) {
164 /* bad block(s), advance mtdoffset */ 181 /* bad block(s), advance mtdoffset */
165 if (IS_NANDDUMP && !(opts & OPT_b)) { 182 if (IS_NANDDUMP) {
166 int bad_len = MIN(tmp, end_addr) - mtdoffset; 183 if (bb_method == BB_PADBAD) {
167 dump_bad(&meminfo, bad_len, opts & OPT_o); 184 int bad_len = MIN(tmp, end_addr) - mtdoffset;
185 dump_bad(&meminfo, bad_len, opts & OPT_o);
186 }
187 /* with option skipbad, increase the total length */
188 if (bb_method == BB_SKIPBAD) {
189 end_addr += (tmp - blockstart);
190 }
168 } 191 }
169 mtdoffset = tmp; 192 mtdoffset = tmp;
170 } 193 }
@@ -182,9 +205,19 @@ int nandwrite_main(int argc UNUSED_PARAM, char **argv)
182 mtdoffset = next_good_eraseblock(fd, &meminfo, blockstart); 205 mtdoffset = next_good_eraseblock(fd, &meminfo, blockstart);
183 if (IS_NANDWRITE) 206 if (IS_NANDWRITE)
184 printf("Writing at 0x%08x\n", mtdoffset); 207 printf("Writing at 0x%08x\n", mtdoffset);
185 else if (mtdoffset > blockstart && !(opts & OPT_b)) { 208 else if (mtdoffset > blockstart) {
186 int bad_len = MIN(mtdoffset, limit) - blockstart; 209 if (bb_method == BB_PADBAD) {
187 dump_bad(&meminfo, bad_len, opts & OPT_o); 210 /* dump FF padded bad block */
211 int bad_len = MIN(mtdoffset, limit) - blockstart;
212 dump_bad(&meminfo, bad_len, opts & OPT_o);
213 } else if (bb_method == BB_SKIPBAD) {
214 /* for skipbad, increase the length */
215 if ((end_addr + mtdoffset - blockstart) > end_addr)
216 end_addr += (mtdoffset - blockstart);
217 else
218 end_addr = ~0;
219 limit = MIN(meminfo.size, end_addr);
220 }
188 } 221 }
189 if (mtdoffset >= limit) 222 if (mtdoffset >= limit)
190 break; 223 break;
diff --git a/modutils/modprobe-small.c b/modutils/modprobe-small.c
index 91e0c1380..b7990bff1 100644
--- a/modutils/modprobe-small.c
+++ b/modutils/modprobe-small.c
@@ -297,7 +297,7 @@ static int pathname_matches_modname(const char *pathname, const char *modname)
297 char name[MODULE_NAME_LEN]; 297 char name[MODULE_NAME_LEN];
298 const char *fname = bb_get_last_path_component_nostrip(pathname); 298 const char *fname = bb_get_last_path_component_nostrip(pathname);
299 const char *suffix = strrstr(fname, ".ko"); 299 const char *suffix = strrstr(fname, ".ko");
300 safe_strncpy(name, fname, suffix - fname); 300 safe_strncpy(name, fname, suffix - fname + 1);
301 replace(name, '-', '_'); 301 replace(name, '-', '_');
302 r = (strcmp(name, modname) == 0); 302 r = (strcmp(name, modname) == 0);
303 return r; 303 return r;
diff --git a/networking/ftpd.c b/networking/ftpd.c
index 33db964fa..2d2a3a44c 100644
--- a/networking/ftpd.c
+++ b/networking/ftpd.c
@@ -623,13 +623,7 @@ popen_ls(const char *opt)
623 623
624 argv[0] = "ftpd"; 624 argv[0] = "ftpd";
625 argv[1] = opt; /* "-l" or "-1" */ 625 argv[1] = opt; /* "-l" or "-1" */
626#if BB_MMU
627 argv[2] = "--"; 626 argv[2] = "--";
628#else
629 /* NOMMU ftpd ls helper chdirs to argv[2],
630 * preventing peer from seeing real root. */
631 argv[2] = xrealloc_getcwd_or_warn(NULL);
632#endif
633 argv[3] = G.ftp_arg; 627 argv[3] = G.ftp_arg;
634 argv[4] = NULL; 628 argv[4] = NULL;
635 629
@@ -650,17 +644,10 @@ popen_ls(const char *opt)
650 /*fflush_all(); - so far we dont use stdio on output */ 644 /*fflush_all(); - so far we dont use stdio on output */
651 pid = BB_MMU ? xfork() : xvfork(); 645 pid = BB_MMU ? xfork() : xvfork();
652 if (pid == 0) { 646 if (pid == 0) {
653 /* child */
654#if !BB_MMU 647#if !BB_MMU
655 /* On NOMMU, we want to execute a child - copy of ourself. 648 int cur_fd;
656 * In chroot we usually can't do it. Thus we chdir
657 * out of the chroot back to original root,
658 * and (see later below) execute bb_busybox_exec_path
659 * relative to current directory */
660 if (fchdir(G.root_fd) != 0)
661 _exit(127);
662 /*close(G.root_fd); - close_on_exec_on() took care of this */
663#endif 649#endif
650 /* child */
664 /* NB: close _first_, then move fd! */ 651 /* NB: close _first_, then move fd! */
665 close(outfd.rd); 652 close(outfd.rd);
666 xmove_fd(outfd.wr, STDOUT_FILENO); 653 xmove_fd(outfd.wr, STDOUT_FILENO);
@@ -674,19 +661,26 @@ popen_ls(const char *opt)
674 /* memset(&G, 0, sizeof(G)); - ls_main does it */ 661 /* memset(&G, 0, sizeof(G)); - ls_main does it */
675 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv)); 662 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
676#else 663#else
677 /* + 1: we must use relative path here if in chroot. 664 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
678 * For example, execv("/proc/self/exe") will fail, since 665 /* On NOMMU, we want to execute a child - copy of ourself
679 * it looks for "/proc/self/exe" _relative to chroot!_ */ 666 * in order to unblock parent after vfork.
680 execv(bb_busybox_exec_path + 1, (char**) argv); 667 * In chroot we usually can't re-exec. Thus we escape
668 * out of the chroot back to original root.
669 */
670 if (G.root_fd >= 0) {
671 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
672 _exit(127);
673 /*close(G.root_fd); - close_on_exec_on() took care of this */
674 }
675 /* Child expects directory to list on fd #3 */
676 xmove_fd(cur_fd, 3);
677 execv(bb_busybox_exec_path, (char**) argv);
681 _exit(127); 678 _exit(127);
682#endif 679#endif
683 } 680 }
684 681
685 /* parent */ 682 /* parent */
686 close(outfd.wr); 683 close(outfd.wr);
687#if !BB_MMU
688 free((char*)argv[2]);
689#endif
690 return outfd.rd; 684 return outfd.rd;
691} 685}
692 686
@@ -705,10 +699,9 @@ handle_dir_common(int opts)
705 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) 699 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
706 return; /* port_or_pasv_was_seen emitted error response */ 700 return; /* port_or_pasv_was_seen emitted error response */
707 701
708 /* -n prevents user/groupname display,
709 * which can be problematic in chroot */
710 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); 702 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
711 ls_fp = xfdopen_for_read(ls_fd); 703 ls_fp = xfdopen_for_read(ls_fd);
704/* FIXME: filenames with embedded newlines are mishandled */
712 705
713 if (opts & USE_CTRL_CONN) { 706 if (opts & USE_CTRL_CONN) {
714 /* STAT <filename> */ 707 /* STAT <filename> */
@@ -729,16 +722,20 @@ handle_dir_common(int opts)
729 int remote_fd = get_remote_transfer_fd(" Directory listing"); 722 int remote_fd = get_remote_transfer_fd(" Directory listing");
730 if (remote_fd >= 0) { 723 if (remote_fd >= 0) {
731 while (1) { 724 while (1) {
732 line = xmalloc_fgetline(ls_fp); 725 unsigned len;
726
727 line = xmalloc_fgets(ls_fp);
733 if (!line) 728 if (!line)
734 break; 729 break;
735 /* I've seen clients complaining when they 730 /* I've seen clients complaining when they
736 * are fed with ls output with bare '\n'. 731 * are fed with ls output with bare '\n'.
737 * Pity... that would be much simpler. 732 * Replace trailing "\n\0" with "\r\n".
738 */ 733 */
739/* TODO: need to s/LF/NUL/g here */ 734 len = strlen(line);
740 xwrite_str(remote_fd, line); 735 if (len != 0) /* paranoia check */
741 xwrite(remote_fd, "\r\n", 2); 736 line[len - 1] = '\r';
737 line[len] = '\n';
738 xwrite(remote_fd, line, len + 1);
742 free(line); 739 free(line);
743 } 740 }
744 } 741 }
@@ -1085,6 +1082,8 @@ enum {
1085 const_PASV = mk_const4('P', 'A', 'S', 'V'), 1082 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1086 const_PORT = mk_const4('P', 'O', 'R', 'T'), 1083 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1087 const_PWD = mk_const3('P', 'W', 'D'), 1084 const_PWD = mk_const3('P', 'W', 'D'),
1085 /* Same as PWD. Reportedly used by windows ftp client */
1086 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1088 const_QUIT = mk_const4('Q', 'U', 'I', 'T'), 1087 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1089 const_REST = mk_const4('R', 'E', 'S', 'T'), 1088 const_REST = mk_const4('R', 'E', 'S', 'T'),
1090 const_RETR = mk_const4('R', 'E', 'T', 'R'), 1089 const_RETR = mk_const4('R', 'E', 'T', 'R'),
@@ -1132,11 +1131,10 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
1132 opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S); 1131 opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1133 if (opts & (OPT_l|OPT_1)) { 1132 if (opts & (OPT_l|OPT_1)) {
1134 /* Our secret backdoor to ls */ 1133 /* Our secret backdoor to ls */
1135/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
1136/* TODO: pass -A? It shows dot files */ 1134/* TODO: pass -A? It shows dot files */
1137/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */ 1135/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1138 xchdir(argv[2]); 1136 if (fchdir(3) != 0)
1139 argv[2] = (char*)"--"; 1137 _exit(127);
1140 /* memset(&G, 0, sizeof(G)); - ls_main does it */ 1138 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1141 return ls_main(argc, argv); 1139 return ls_main(argc, argv);
1142 } 1140 }
@@ -1175,12 +1173,15 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
1175 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid()); 1173 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1176 1174
1177#if !BB_MMU 1175#if !BB_MMU
1178 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY); 1176 G.root_fd = -1;
1179 close_on_exec_on(G.root_fd);
1180#endif 1177#endif
1181 1178 argv += optind;
1182 if (argv[optind]) { 1179 if (argv[0]) {
1183 xchroot(argv[optind]); 1180#if !BB_MMU
1181 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1182 close_on_exec_on(G.root_fd);
1183#endif
1184 xchroot(argv[0]);
1184 } 1185 }
1185 1186
1186 //umask(077); - admin can set umask before starting us 1187 //umask(077); - admin can set umask before starting us
@@ -1292,7 +1293,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
1292 WRITE_OK(FTP_ALLOOK); 1293 WRITE_OK(FTP_ALLOOK);
1293 else if (cmdval == const_SYST) 1294 else if (cmdval == const_SYST)
1294 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n"); 1295 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1295 else if (cmdval == const_PWD) 1296 else if (cmdval == const_PWD || cmdval == const_XPWD)
1296 handle_pwd(); 1297 handle_pwd();
1297 else if (cmdval == const_CWD) 1298 else if (cmdval == const_CWD)
1298 handle_cwd(); 1299 handle_cwd();
diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c
index f8a67d9ee..ec4d8ba03 100644
--- a/networking/libiproute/iproute.c
+++ b/networking/libiproute/iproute.c
@@ -73,7 +73,7 @@ static unsigned get_hz(void)
73 fclose(fp); 73 fclose(fp);
74 } 74 }
75 if (!hz_internal) 75 if (!hz_internal)
76 hz_internal = sysconf(_SC_CLK_TCK); 76 hz_internal = bb_clk_tck();
77 return hz_internal; 77 return hz_internal;
78} 78}
79 79
diff --git a/networking/route.c b/networking/route.c
index 4235ea72c..24cc2eb5a 100644
--- a/networking/route.c
+++ b/networking/route.c
@@ -283,7 +283,7 @@ static NOINLINE void INET_setroute(int action, char **args)
283 if (k == KW_IPVx_IRTT) { 283 if (k == KW_IPVx_IRTT) {
284 rt->rt_flags |= RTF_IRTT; 284 rt->rt_flags |= RTF_IRTT;
285 rt->rt_irtt = xatoul(args_m1); 285 rt->rt_irtt = xatoul(args_m1);
286 rt->rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */ 286 rt->rt_irtt *= (bb_clk_tck() / 100); /* FIXME */
287#if 0 /* FIXME: do we need to check anything of this? */ 287#if 0 /* FIXME: do we need to check anything of this? */
288 if (rt->rt_irtt < 1 || rt->rt_irtt > (120 * HZ)) { 288 if (rt->rt_irtt < 1 || rt->rt_irtt > (120 * HZ)) {
289 bb_error_msg_and_die("bad irtt"); 289 bb_error_msg_and_die("bad irtt");
diff --git a/networking/udhcp/Config.src b/networking/udhcp/Config.src
index 6bfa398ea..c34c8d6f0 100644
--- a/networking/udhcp/Config.src
+++ b/networking/udhcp/Config.src
@@ -84,6 +84,17 @@ config FEATURE_UDHCPC_ARPING
84 will DHCPDECLINE the offer if the address is in use, 84 will DHCPDECLINE the offer if the address is in use,
85 and restart the discover process. 85 and restart the discover process.
86 86
87config FEATURE_UDHCPC_SANITIZEOPT
88 bool "Do not pass malformed host and domain names"
89 default y
90 depends on UDHCPC
91 help
92 If selected, udhcpc will check some options (such as option 12 -
93 hostname) and if they don't look like valid hostnames
94 (for example, if they start with dash or contain spaces),
95 they will be replaced with string "bad" when exporting
96 to the environment.
97
87config FEATURE_UDHCP_PORT 98config FEATURE_UDHCP_PORT
88 bool "Enable '-P port' option for udhcpd and udhcpc" 99 bool "Enable '-P port' option for udhcpd and udhcpc"
89 default n 100 default n
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 7dfc160e2..e468b7bbb 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -136,6 +136,7 @@ static int mton(uint32_t mask)
136 return i; 136 return i;
137} 137}
138 138
139#if ENABLE_FEATURE_UDHCPC_SANITIZEOPT
139/* Check if a given label represents a valid DNS label 140/* Check if a given label represents a valid DNS label
140 * Return pointer to the first character after the label upon success, 141 * Return pointer to the first character after the label upon success,
141 * NULL otherwise. 142 * NULL otherwise.
@@ -192,6 +193,9 @@ static int good_hostname(const char *name)
192 name++; 193 name++;
193 } 194 }
194} 195}
196#else
197# define good_hostname(name) 1
198#endif
195 199
196/* Create "opt_name=opt_value" string */ 200/* Create "opt_name=opt_value" string */
197static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name) 201static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name)
diff --git a/procps/iostat.c b/procps/iostat.c
index 978d23430..8d272c87c 100644
--- a/procps/iostat.c
+++ b/procps/iostat.c
@@ -109,11 +109,6 @@ enum {
109 OPT_m = 1 << 5, 109 OPT_m = 1 << 5,
110}; 110};
111 111
112static ALWAYS_INLINE unsigned get_user_hz(void)
113{
114 return sysconf(_SC_CLK_TCK);
115}
116
117static ALWAYS_INLINE int this_is_smp(void) 112static ALWAYS_INLINE int this_is_smp(void)
118{ 113{
119 return (G.total_cpus > 1); 114 return (G.total_cpus > 1);
@@ -414,7 +409,7 @@ int iostat_main(int argc UNUSED_PARAM, char **argv)
414 memset(&stats_data, 0, sizeof(stats_data)); 409 memset(&stats_data, 0, sizeof(stats_data));
415 410
416 /* Get number of clock ticks per sec */ 411 /* Get number of clock ticks per sec */
417 G.clk_tck = get_user_hz(); 412 G.clk_tck = bb_clk_tck();
418 413
419 /* Determine number of CPUs */ 414 /* Determine number of CPUs */
420 G.total_cpus = get_cpu_count(); 415 G.total_cpus = get_cpu_count();
diff --git a/procps/mpstat.c b/procps/mpstat.c
index aa5a5c73f..c628d6215 100644
--- a/procps/mpstat.c
+++ b/procps/mpstat.c
@@ -775,12 +775,6 @@ static void main_loop(void)
775 775
776/* Initialization */ 776/* Initialization */
777 777
778/* Get number of clock ticks per sec */
779static ALWAYS_INLINE unsigned get_hz(void)
780{
781 return sysconf(_SC_CLK_TCK);
782}
783
784static void alloc_struct(int cpus) 778static void alloc_struct(int cpus)
785{ 779{
786 int i; 780 int i;
@@ -873,7 +867,7 @@ int mpstat_main(int UNUSED_PARAM argc, char **argv)
873 G.cpu_nr = get_cpu_count(); 867 G.cpu_nr = get_cpu_count();
874 868
875 /* Get number of clock ticks per sec */ 869 /* Get number of clock ticks per sec */
876 G.hz = get_hz(); 870 G.hz = bb_clk_tck();
877 871
878 /* Calculate number of interrupts per processor */ 872 /* Calculate number of interrupts per processor */
879 G.irqcpu_nr = get_irqcpu_nr(PROCFS_INTERRUPTS, NR_IRQS) + NR_IRQCPU_PREALLOC; 873 G.irqcpu_nr = get_irqcpu_nr(PROCFS_INTERRUPTS, NR_IRQS) + NR_IRQCPU_PREALLOC;
diff --git a/shell/ash.c b/shell/ash.c
index c3e950893..550a98db6 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -13222,7 +13222,7 @@ timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13222 const unsigned char *p; 13222 const unsigned char *p;
13223 struct tms buf; 13223 struct tms buf;
13224 13224
13225 clk_tck = sysconf(_SC_CLK_TCK); 13225 clk_tck = bb_clk_tck();
13226 times(&buf); 13226 times(&buf);
13227 13227
13228 p = timescmd_str; 13228 p = timescmd_str;
diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c
index a6a4ff25c..f75851085 100644
--- a/sysklogd/syslogd.c
+++ b/sysklogd/syslogd.c
@@ -648,22 +648,24 @@ static void log_locally(time_t now, char *msg, logFile_t *log_file)
648 } 648 }
649 /* newFile == "f.0" now */ 649 /* newFile == "f.0" now */
650 rename(log_file->path, newFile); 650 rename(log_file->path, newFile);
651 /* Incredibly, if F and F.0 are hardlinks, POSIX 651 }
652 * _demands_ that rename returns 0 but does not 652
653 * remove F!!! 653 /* We may or may not have just renamed the file away;
654 * (hardlinked F/F.0 pair was observed after 654 * if we didn't rename because we aren't keeping any backlog,
655 * power failure during rename()). 655 * then it's time to clobber the file. If we did rename it...,
656 * Ensure old file is gone: 656 * incredibly, if F and F.0 are hardlinks, POSIX _demands_
657 */ 657 * that rename returns 0 but does not remove F!!!
658 unlink(log_file->path); 658 * (hardlinked F/F.0 pair was observed after
659 * power failure during rename()).
660 * So ensure old file is gone in any case:
661 */
662 unlink(log_file->path);
659#ifdef SYSLOGD_WRLOCK 663#ifdef SYSLOGD_WRLOCK
660 fl.l_type = F_UNLCK; 664 fl.l_type = F_UNLCK;
661 fcntl(log_file->fd, F_SETLKW, &fl); 665 fcntl(log_file->fd, F_SETLKW, &fl);
662#endif 666#endif
663 close(log_file->fd); 667 close(log_file->fd);
664 goto reopen; 668 goto reopen;
665 }
666 ftruncate(log_file->fd, 0);
667 } 669 }
668 log_file->size += 670 log_file->size +=
669#endif 671#endif
diff --git a/testsuite/awk.tests b/testsuite/awk.tests
index 132afc6a9..9e6952ffd 100755
--- a/testsuite/awk.tests
+++ b/testsuite/awk.tests
@@ -295,6 +295,22 @@ testing "awk -e and ARGC" \
295 "" 295 ""
296SKIP= 296SKIP=
297 297
298# The examples are in fact not valid awk programs (break/continue
299# can only be used inside loops).
300# But we do accept them outside of loops.
301# We had a bug with misparsing "break ; else" sequence.
302# Test that *that* bug is fixed, using simplest possible scripts:
303testing "awk break" \
304 "awk -f - 2>&1; echo \$?" \
305 "0\n" \
306 "" \
307 'BEGIN { if (1) break; else a = 1 }'
308testing "awk continue" \
309 "awk -f - 2>&1; echo \$?" \
310 "0\n" \
311 "" \
312 'BEGIN { if (1) continue; else a = 1 }'
313
298# testing "description" "command" "result" "infile" "stdin" 314# testing "description" "command" "result" "infile" "stdin"
299 315
300exit $FAILCOUNT 316exit $FAILCOUNT
diff --git a/testsuite/find.tests b/testsuite/find.tests
index 345d1e82e..f041106c3 100755
--- a/testsuite/find.tests
+++ b/testsuite/find.tests
@@ -15,6 +15,32 @@ testing "find -type f" \
15 "./testfile\n" \ 15 "./testfile\n" \
16 "" "" 16 "" ""
17 17
18optional FEATURE_FIND_EXEC
19testing "find -exec exitcode 1" \
20 "cd find.tempdir && find testfile -exec true {} \; 2>&1; echo \$?" \
21 "0\n" \
22 "" ""
23SKIP=
24optional FEATURE_FIND_EXEC_PLUS
25testing "find -exec exitcode 2" \
26 "cd find.tempdir && find testfile -exec true {} + 2>&1; echo \$?" \
27 "0\n" \
28 "" ""
29SKIP=
30# Surprisingly, "-exec false ;" results in exitcode 0! "-exec false +" is different!!!
31optional FEATURE_FIND_EXEC
32testing "find -exec exitcode 3" \
33 "cd find.tempdir && find testfile -exec false {} \; 2>&1; echo \$?" \
34 "0\n" \
35 "" ""
36SKIP=
37optional FEATURE_FIND_EXEC_PLUS
38testing "find -exec exitcode 4" \
39 "cd find.tempdir && find testfile -exec false {} + 2>&1; echo \$?" \
40 "1\n" \
41 "" ""
42SKIP=
43
18# testing "description" "command" "result" "infile" "stdin" 44# testing "description" "command" "result" "infile" "stdin"
19 45
20rm -rf find.tempdir 46rm -rf find.tempdir
diff --git a/util-linux/fatattr.c b/util-linux/fatattr.c
new file mode 100644
index 000000000..0f8d63268
--- /dev/null
+++ b/util-linux/fatattr.c
@@ -0,0 +1,104 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * Display or change file attributes on a fat file system
4 *
5 * Copyright 2005 H. Peter Anvin
6 * Busybox'ed (2014) by Pascal Bellard <pascal.bellard@ads-lu.com>
7 *
8 * This file can be redistributed under the terms of the GNU General
9 * Public License
10 */
11//config:config FATATTR
12//config: bool "fatattr"
13//config: default y
14//config: select PLATFORM_LINUX
15//config: help
16//config: fatattr lists or changes the file attributes on a fat file system.
17
18//applet:IF_FATATTR(APPLET(fatattr, BB_DIR_BIN, BB_SUID_DROP))
19//kbuild:lib-$(CONFIG_FATATTR) += fatattr.o
20
21//usage:#define fatattr_trivial_usage
22//usage: "[-+rhsvda] FILE..."
23//usage:#define fatattr_full_usage "\n\n"
24//usage: "Change file attributes on FAT filesystem\n"
25//usage: "\n - Clear attributes"
26//usage: "\n + Set attributes"
27//usage: "\n r Read only"
28//usage: "\n h Hidden"
29//usage: "\n s System"
30//usage: "\n v Volume label"
31//usage: "\n d Directory"
32//usage: "\n a Archive"
33
34#include "libbb.h"
35/* linux/msdos_fs.h says: */
36#ifndef FAT_IOCTL_GET_ATTRIBUTES
37# define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32)
38# define FAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32)
39#endif
40
41/* Currently supports only the FAT flags, not the NTFS ones.
42 * Extra space at the end is a hack to print space separator in file listing.
43 * Let's hope no one ever passes space as an option char :)
44 */
45static const char bit_to_char[] = "rhsvda67 ";
46
47static inline unsigned long get_flag(char c)
48{
49 const char *fp = strchr(bit_to_char, c);
50 if (!fp)
51 bb_error_msg_and_die("invalid character '%c'", c);
52 return 1 << (fp - bit_to_char);
53}
54
55static unsigned decode_arg(const char *arg)
56{
57 unsigned fl = 0;
58 while (*++arg)
59 fl |= get_flag(*arg);
60 return fl;
61}
62
63int fatattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
64int fatattr_main(int argc UNUSED_PARAM, char **argv)
65{
66 unsigned set_mask = 0;
67 unsigned clear_mask = 0;
68
69 for (;;) {
70 unsigned fl;
71 char *arg = *++argv;
72
73 if (!arg)
74 bb_show_usage();
75 if (arg[0] != '-' && arg[0] != '+')
76 break;
77 fl = decode_arg(arg);
78 if (arg[0] == '+')
79 set_mask |= fl;
80 else
81 clear_mask |= fl;
82 }
83
84 do {
85 int fd, i;
86 uint32_t attr;
87
88 fd = xopen(*argv, O_RDONLY);
89 xioctl(fd, FAT_IOCTL_GET_ATTRIBUTES, &attr);
90 attr = (attr | set_mask) & ~clear_mask;
91 if (set_mask | clear_mask)
92 xioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr);
93 else {
94 for (i = 0; bit_to_char[i]; i++) {
95 bb_putchar((attr & 1) ? bit_to_char[i] : ' ');
96 attr >>= 1;
97 }
98 puts(*argv);
99 }
100 close(fd);
101 } while (*++argv);
102
103 return EXIT_SUCCESS;
104}