aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--archival/bbunzip.c10
-rw-r--r--archival/bzip2.c4
-rw-r--r--archival/cpio.c8
-rw-r--r--archival/gzip.c4
-rw-r--r--archival/libarchive/decompress_unlzma.c5
-rw-r--r--archival/libarchive/get_header_cpio.c3
-rw-r--r--archival/lzop.c1
-rw-r--r--archival/tar.c2
-rw-r--r--archival/unzip.c4
-rw-r--r--console-tools/loadfont.c2
-rw-r--r--console-tools/showkey.c2
-rw-r--r--coreutils/chgrp.c8
-rw-r--r--coreutils/chmod.c7
-rw-r--r--coreutils/chown.c10
-rw-r--r--coreutils/cksum.c54
-rw-r--r--coreutils/cp.c199
-rw-r--r--coreutils/cut.c208
-rw-r--r--coreutils/df.c45
-rw-r--r--coreutils/du.c33
-rw-r--r--coreutils/echo.c6
-rw-r--r--coreutils/env.c19
-rw-r--r--coreutils/expr.c4
-rw-r--r--coreutils/head.c13
-rw-r--r--coreutils/id.c2
-rw-r--r--coreutils/ls.c30
-rw-r--r--coreutils/mv.c76
-rw-r--r--coreutils/nproc.c4
-rw-r--r--coreutils/shred.c27
-rw-r--r--coreutils/sort.c2
-rw-r--r--coreutils/tail.c67
-rw-r--r--coreutils/touch.c2
-rw-r--r--coreutils/tty.c2
-rw-r--r--coreutils/uname.c4
-rw-r--r--coreutils/uniq.c24
-rw-r--r--coreutils/uudecode.c10
-rw-r--r--coreutils/who.c2
-rw-r--r--coreutils/yes.c2
-rw-r--r--debianutils/run_parts.c15
-rw-r--r--docs/busybox_footer.pod1
-rw-r--r--e2fsprogs/chattr.c145
-rw-r--r--e2fsprogs/e2fs_lib.c204
-rw-r--r--e2fsprogs/e2fs_lib.h20
-rw-r--r--e2fsprogs/lsattr.c73
-rw-r--r--editors/awk.c2016
-rw-r--r--editors/cmp.c2
-rw-r--r--editors/sed.c2
-rw-r--r--editors/vi.c412
-rw-r--r--examples/udhcp/udhcpd.conf9
-rw-r--r--findutils/grep.c6
-rw-r--r--include/bb_e2fs_defs.h23
-rw-r--r--include/dump.h4
-rw-r--r--include/libbb.h44
-rw-r--r--include/platform.h2
-rw-r--r--libbb/copy_file.c2
-rw-r--r--libbb/dump.c41
-rw-r--r--libbb/getopt32.c11
-rw-r--r--libbb/iterate_on_dir.c28
-rw-r--r--libbb/remove_file.c6
-rw-r--r--libbb/signals.c2
-rw-r--r--libbb/xfuncs.c6
-rw-r--r--loginutils/login.c3
-rw-r--r--loginutils/su.c2
-rw-r--r--miscutils/ascii.c51
-rw-r--r--miscutils/bc.c75
-rw-r--r--miscutils/chat.c2
-rw-r--r--miscutils/flashcp.c4
-rw-r--r--miscutils/i2c_tools.c2
-rw-r--r--miscutils/man.c2
-rw-r--r--miscutils/microcom.c14
-rw-r--r--miscutils/mt.c2
-rw-r--r--miscutils/strings.c4
-rw-r--r--miscutils/ts.c5
-rw-r--r--miscutils/ubi_tools.c2
-rw-r--r--modutils/modinfo.c2
-rw-r--r--modutils/modprobe.c3
-rw-r--r--networking/ftpd.c4
-rwxr-xr-xnetworking/httpd_post_upload.cgi2
-rw-r--r--networking/nameif.c4
-rw-r--r--networking/nslookup.c2
-rw-r--r--networking/tc.c42
-rw-r--r--networking/tcpudp.c33
-rw-r--r--networking/telnetd.c1
-rw-r--r--networking/tftp.c5
-rw-r--r--networking/udhcp/arpping.c8
-rw-r--r--networking/udhcp/common.c74
-rw-r--r--networking/udhcp/common.h13
-rw-r--r--networking/udhcp/d6_dhcpc.c254
-rw-r--r--networking/udhcp/d6_packet.c3
-rw-r--r--networking/udhcp/dhcpc.c425
-rw-r--r--networking/udhcp/dhcpc.h5
-rw-r--r--networking/udhcp/dhcpd.c11
-rw-r--r--networking/udhcp/packet.c3
-rw-r--r--networking/udhcp/socket.c2
-rw-r--r--networking/vconfig.c12
-rw-r--r--procps/free.c80
-rw-r--r--procps/kill.c2
-rw-r--r--procps/lsof.c2
-rw-r--r--procps/pgrep.c12
-rw-r--r--procps/sysctl.c4
-rw-r--r--procps/top.c4
-rw-r--r--procps/watch.c2
-rw-r--r--runit/runsv.c39
-rw-r--r--runit/svlogd.c43
-rwxr-xr-xscripts/gen_build_files.sh2
-rw-r--r--scripts/kconfig/confdata.c17
-rw-r--r--shell/ash.c208
-rw-r--r--shell/ash_remove_unnecessary_code_in_backquote_expansion.patch135
-rw-r--r--shell/ash_test/ash-misc/control_char3.right1
-rwxr-xr-xshell/ash_test/ash-misc/control_char3.tests2
-rw-r--r--shell/ash_test/ash-misc/control_char4.right1
-rwxr-xr-xshell/ash_test/ash-misc/control_char4.tests2
-rw-r--r--shell/ash_test/ash-parsing/bkslash_newline4.right4
-rwxr-xr-xshell/ash_test/ash-parsing/bkslash_newline4.tests14
-rw-r--r--shell/ash_test/ash-psubst/bash_procsub.right9
-rwxr-xr-xshell/ash_test/ash-psubst/bash_procsub.tests33
-rw-r--r--shell/ash_test/ash-psubst/falsetick.right24
-rwxr-xr-xshell/ash_test/ash-psubst/falsetick.tests19
-rw-r--r--shell/ash_test/ash-psubst/falsetick2.right1
-rwxr-xr-xshell/ash_test/ash-psubst/falsetick2.tests3
-rw-r--r--shell/ash_test/ash-vars/var6.right2
-rwxr-xr-xshell/ash_test/ash-vars/var6.tests5
-rw-r--r--shell/ash_test/ash-vars/var_nested1.right3
-rwxr-xr-xshell/ash_test/ash-vars/var_nested1.tests16
-rw-r--r--shell/ash_test/ash-vars/var_nested2.right1
-rwxr-xr-xshell/ash_test/ash-vars/var_nested2.tests2
-rw-r--r--shell/hush.c177
-rw-r--r--shell/hush_test/hush-misc/control_char3.right1
-rwxr-xr-xshell/hush_test/hush-misc/control_char3.tests2
-rw-r--r--shell/hush_test/hush-misc/control_char4.right1
-rwxr-xr-xshell/hush_test/hush-misc/control_char4.tests2
-rw-r--r--shell/hush_test/hush-parsing/bkslash_newline4.right4
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_newline4.tests14
-rw-r--r--shell/hush_test/hush-psubst/falsetick.right3
-rwxr-xr-xshell/hush_test/hush-psubst/falsetick.tests3
-rw-r--r--shell/hush_test/hush-psubst/falsetick3.right3
-rwxr-xr-xshell/hush_test/hush-psubst/falsetick3.tests3
-rw-r--r--shell/hush_test/hush-quoting/dollar_squote_bash1.right10
-rwxr-xr-xshell/hush_test/hush-quoting/dollar_squote_bash1.tests8
-rw-r--r--shell/hush_test/hush-quoting/dollar_squote_bash2.right6
-rwxr-xr-xshell/hush_test/hush-quoting/dollar_squote_bash2.tests10
-rw-r--r--shell/hush_test/hush-vars/var6.right2
-rwxr-xr-xshell/hush_test/hush-vars/var6.tests7
-rw-r--r--shell/hush_test/hush-vars/var_bash7.right1
-rwxr-xr-xshell/hush_test/hush-vars/var_bash7.tests1
-rw-r--r--shell/match.c3
-rw-r--r--shell/shell_common.c4
-rw-r--r--sysklogd/klogd.c2
-rw-r--r--sysklogd/logger.c2
-rwxr-xr-xtestsuite/awk.tests59
-rwxr-xr-xtestsuite/cut.tests64
-rw-r--r--testsuite/mv/mv-files-to-dir-216
-rwxr-xr-xtestsuite/unlzma.tests17
-rw-r--r--testsuite/unlzma_issue_3.lzmabin0 -> 27 bytes
-rw-r--r--util-linux/blockdev.c28
-rw-r--r--util-linux/dmesg.c2
-rw-r--r--util-linux/fdisk.c4
-rw-r--r--util-linux/flock.c2
-rw-r--r--util-linux/hexdump_xxd.c63
-rw-r--r--util-linux/ionice.c18
-rw-r--r--util-linux/mountpoint.c9
-rw-r--r--util-linux/readprofile.c4
-rw-r--r--util-linux/renice.c2
-rw-r--r--util-linux/switch_root.c35
-rw-r--r--util-linux/taskset.c108
164 files changed, 4114 insertions, 2333 deletions
diff --git a/archival/bbunzip.c b/archival/bbunzip.c
index d639f307e..0ac059c19 100644
--- a/archival/bbunzip.c
+++ b/archival/bbunzip.c
@@ -284,7 +284,7 @@ int uncompress_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
-//usage: "\n -t Test file integrity"
+//usage: "\n -t Test integrity"
//usage:
//usage:#define gunzip_example_usage
//usage: "$ ls -la /tmp/BusyBox*\n"
@@ -407,6 +407,8 @@ int gunzip_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
+//usage: "\n -t Test integrity"
+//usage:
//usage:#define bzcat_trivial_usage
//usage: "[FILE]..."
//usage:#define bzcat_full_usage "\n\n"
@@ -465,6 +467,7 @@ int bunzip2_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
+//usage: "\n -t Test integrity"
//usage:
//usage:#define lzma_trivial_usage
//usage: "-d [-cfk] [FILE]..."
@@ -474,6 +477,7 @@ int bunzip2_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
+//usage: "\n -t Test integrity"
//usage:
//usage:#define lzcat_trivial_usage
//usage: "[FILE]..."
@@ -536,7 +540,7 @@ int unlzma_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
-//usage: "\n -t Test file integrity"
+//usage: "\n -t Test integrity"
//usage:
//usage:#define xz_trivial_usage
//usage: "-d [-cfk] [FILE]..."
@@ -546,7 +550,7 @@ int unlzma_main(int argc UNUSED_PARAM, char **argv)
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
-//usage: "\n -t Test file integrity"
+//usage: "\n -t Test integrity"
//usage:
//usage:#define xzcat_trivial_usage
//usage: "[FILE]..."
diff --git a/archival/bzip2.c b/archival/bzip2.c
index ac5db0880..bce13cf93 100644
--- a/archival/bzip2.c
+++ b/archival/bzip2.c
@@ -56,11 +56,13 @@
//usage: "\n -1..9 Compression level"
//usage: IF_FEATURE_BZIP2_DECOMPRESS(
//usage: "\n -d Decompress"
-//usage: "\n -t Test file integrity"
//usage: )
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
+//usage: IF_FEATURE_BZIP2_DECOMPRESS(
+//usage: "\n -t Test integrity"
+//usage: )
#include "libbb.h"
#include "bb_archive.h"
diff --git a/archival/cpio.c b/archival/cpio.c
index 94303389e..d84f6937d 100644
--- a/archival/cpio.c
+++ b/archival/cpio.c
@@ -418,7 +418,8 @@ int cpio_main(int argc UNUSED_PARAM, char **argv)
if (argv[0] == NULL)
bb_show_usage();
if (opt & OPT_CREATE_LEADING_DIR)
- mkdir(argv[0], 0777);
+ /* GNU cpio 2.13: "cpio -d -p a/b/c" works */
+ bb_make_directory(argv[0], -1, FILEUTILS_RECUR);
/* Crude existence check:
* close(xopen(argv[0], O_RDONLY | O_DIRECTORY));
* We can also xopen, fstat, IS_DIR, later fchdir.
@@ -428,6 +429,11 @@ int cpio_main(int argc UNUSED_PARAM, char **argv)
* a diffrerent problem earlier.
* This is good enough for now.
*/
+//FIXME: GNU cpio -d -p DIR does not immediately create DIR -
+//it just prepends "DIR/" to the names of files to be created.
+//The first file (fails to) be copied, and then the -d logic
+//triggers and creates all necessary directories.
+//IOW: bare "cpio -d -p DIR" + ^C shouldn't create anything.
#if !BB_MMU
pp.rd = 3;
pp.wr = 4;
diff --git a/archival/gzip.c b/archival/gzip.c
index d9c730f13..91bd4d09d 100644
--- a/archival/gzip.c
+++ b/archival/gzip.c
@@ -77,11 +77,13 @@ aa: 85.1% -- replaced with aa.gz
//usage: )
//usage: IF_FEATURE_GZIP_DECOMPRESS(
//usage: "\n -d Decompress"
-//usage: "\n -t Test file integrity"
//usage: )
//usage: "\n -c Write to stdout"
//usage: "\n -f Force"
//usage: "\n -k Keep input files"
+//usage: IF_FEATURE_GZIP_DECOMPRESS(
+//usage: "\n -t Test integrity"
+//usage: )
//usage:
//usage:#define gzip_example_usage
//usage: "$ ls -la /tmp/busybox*\n"
diff --git a/archival/libarchive/decompress_unlzma.c b/archival/libarchive/decompress_unlzma.c
index 0744f231a..fb5aac8fe 100644
--- a/archival/libarchive/decompress_unlzma.c
+++ b/archival/libarchive/decompress_unlzma.c
@@ -290,8 +290,11 @@ unpack_lzma_stream(transformer_state_t *xstate)
uint32_t pos;
pos = buffer_pos - rep0;
- if ((int32_t)pos < 0)
+ if ((int32_t)pos < 0) {
pos += header.dict_size;
+ if ((int32_t)pos < 0)
+ goto bad;
+ }
match_byte = buffer[pos];
do {
int bit;
diff --git a/archival/libarchive/get_header_cpio.c b/archival/libarchive/get_header_cpio.c
index 4ad174732..9ad0557c2 100644
--- a/archival/libarchive/get_header_cpio.c
+++ b/archival/libarchive/get_header_cpio.c
@@ -20,7 +20,7 @@ typedef struct hardlinks_t {
char FAST_FUNC get_header_cpio(archive_handle_t *archive_handle)
{
file_header_t *file_header = archive_handle->file_header;
- char cpio_header[110];
+ char cpio_header[111];
int namesize;
int major, minor, nlink, mode, inode;
unsigned size, uid, gid, mtime;
@@ -43,6 +43,7 @@ char FAST_FUNC get_header_cpio(archive_handle_t *archive_handle)
bb_simple_error_msg_and_die("unsupported cpio format, use newc or crc");
}
+ cpio_header[110] = '\0'; /* sscanf may call strlen which may break without this */
if (sscanf(cpio_header + 6,
"%8x" "%8x" "%8x" "%8x"
"%8x" "%8x" "%8x" /*maj,min:*/ "%*16c"
diff --git a/archival/lzop.c b/archival/lzop.c
index bdd21598c..74df8ff03 100644
--- a/archival/lzop.c
+++ b/archival/lzop.c
@@ -86,6 +86,7 @@
//usage: "\n -f Force"
//usage: "\n -U Delete input files"
///////: "\n -k Keep input files" (default, so why bother documenting?)
+//usage: "\n -t Test integrity"
//usage: "\n -v Verbose"
//usage: "\n -F Don't verify checksum"
diff --git a/archival/tar.c b/archival/tar.c
index 9d4c7b662..4a540b77a 100644
--- a/archival/tar.c
+++ b/archival/tar.c
@@ -775,7 +775,7 @@ static llist_t *append_file_list_to_list(llist_t *list)
//usage: IF_FEATURE_TAR_NOPRESERVE_TIME("m")
//usage: "vokO] "
//usage: "[-f TARFILE] [-C DIR] "
-//usage: IF_FEATURE_TAR_FROM("[-T FILE] [-X FILE] "IF_FEATURE_TAR_LONG_OPTIONS("[OPTION]... "))
+//usage: IF_FEATURE_TAR_FROM("[-T FILE] [-X FILE] "IF_FEATURE_TAR_LONG_OPTIONS("[LONGOPT]... "))
//usage: "[FILE]..."
//usage:#define tar_full_usage "\n\n"
//usage: IF_FEATURE_TAR_CREATE("Create, extract, ")
diff --git a/archival/unzip.c b/archival/unzip.c
index 66005a43e..fc92ac661 100644
--- a/archival/unzip.c
+++ b/archival/unzip.c
@@ -56,14 +56,14 @@
//kbuild:lib-$(CONFIG_UNZIP) += unzip.o
//usage:#define unzip_trivial_usage
-//usage: "[-lnojpq] FILE[.zip] [FILE]... [-x FILE...] [-d DIR]"
+//usage: "[-lnojpq] FILE[.zip] [FILE]... [-x FILE]... [-d DIR]"
//usage:#define unzip_full_usage "\n\n"
//usage: "Extract FILEs from ZIP archive\n"
//usage: "\n -l List contents (with -q for short form)"
//usage: "\n -n Never overwrite files (default: ask)"
//usage: "\n -o Overwrite"
//usage: "\n -j Do not restore paths"
-//usage: "\n -p Print to stdout"
+//usage: "\n -p Write to stdout"
//usage: "\n -t Test"
//usage: "\n -q Quiet"
//usage: "\n -x FILE Exclude FILEs"
diff --git a/console-tools/loadfont.c b/console-tools/loadfont.c
index 7533b0aad..81a0e6aa8 100644
--- a/console-tools/loadfont.c
+++ b/console-tools/loadfont.c
@@ -399,7 +399,7 @@ setfont [-O font+umap.orig] [-o font.orig] [-om cmap.orig]
-V Version
*/
//usage:#define setfont_trivial_usage
-//usage: "FONT [-m MAPFILE] [-C TTY]"
+//usage: "[-m MAPFILE] [-C TTY] FILE"
//usage:#define setfont_full_usage "\n\n"
//usage: "Load a console font\n"
//usage: "\n -m MAPFILE Load console screen map"
diff --git a/console-tools/showkey.c b/console-tools/showkey.c
index 4d7a9b9e5..84eb38a0a 100644
--- a/console-tools/showkey.c
+++ b/console-tools/showkey.c
@@ -106,7 +106,7 @@ int showkey_main(int argc UNUSED_PARAM, char **argv)
xioctl(STDIN_FILENO, KDSKBMODE, (void *)(ptrdiff_t)((option_mask32 & OPT_k) ? K_MEDIUMRAW : K_RAW));
// we should exit on any signal; signals should interrupt read
- bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo);
+ bb_signals_norestart(BB_FATAL_SIGS, record_signo);
// inform user that program ends after time of inactivity
printf(press_keys, "10s after last keypress");
diff --git a/coreutils/chgrp.c b/coreutils/chgrp.c
index 0c2060981..e6ac316e5 100644
--- a/coreutils/chgrp.c
+++ b/coreutils/chgrp.c
@@ -23,13 +23,17 @@
//usage:#define chgrp_trivial_usage
//usage: "[-Rh"IF_DESKTOP("LHPcvf")"]... GROUP FILE..."
//usage:#define chgrp_full_usage "\n\n"
-//usage: "Change the group membership of FILEs to GROUP\n"
-//usage: "\n -R Recurse"
+//usage: "Change the group membership of FILEs to GROUP"
+//usage: "\n"
//usage: "\n -h Affect symlinks instead of symlink targets"
//usage: IF_DESKTOP(
//usage: "\n -L Traverse all symlinks to directories"
//usage: "\n -H Traverse symlinks on command line only"
//usage: "\n -P Don't traverse symlinks (default)"
+//usage: )
+//next 4 options are the same for chmod/chown/chgrp:
+//usage: "\n -R Recurse"
+//usage: IF_DESKTOP(
//usage: "\n -c List changed files"
//usage: "\n -v Verbose"
//usage: "\n -f Hide errors"
diff --git a/coreutils/chmod.c b/coreutils/chmod.c
index d2988c490..e260adab2 100644
--- a/coreutils/chmod.c
+++ b/coreutils/chmod.c
@@ -26,12 +26,13 @@
//usage:#define chmod_trivial_usage
//usage: "[-R"IF_DESKTOP("cvf")"] MODE[,MODE]... FILE..."
//usage:#define chmod_full_usage "\n\n"
-//usage: "Each MODE is one or more of the letters ugoa, one of the\n"
-//usage: "symbols +-= and one or more of the letters rwxst\n"
+//usage: "MODE is octal number (bit pattern sstrwxrwxrwx) or [ugoa]{+|-|=}[rwxXst]"
+//usage: "\n"
+//next 4 options are the same for chmod/chown/chgrp:
//usage: "\n -R Recurse"
//usage: IF_DESKTOP(
//usage: "\n -c List changed files"
-//usage: "\n -v List all files"
+//usage: "\n -v Verbose"
//usage: "\n -f Hide errors"
//usage: )
//usage:
diff --git a/coreutils/chown.c b/coreutils/chown.c
index 170507147..528a2a05a 100644
--- a/coreutils/chown.c
+++ b/coreutils/chown.c
@@ -28,15 +28,19 @@
//usage:#define chown_trivial_usage
//usage: "[-Rh"IF_DESKTOP("LHPcvf")"]... USER[:[GRP]] FILE..."
//usage:#define chown_full_usage "\n\n"
-//usage: "Change the owner and/or group of FILEs to USER and/or GRP\n"
-//usage: "\n -R Recurse"
+//usage: "Change the owner and/or group of FILEs to USER and/or GRP"
+//usage: "\n"
//usage: "\n -h Affect symlinks instead of symlink targets"
//usage: IF_DESKTOP(
//usage: "\n -L Traverse all symlinks to directories"
//usage: "\n -H Traverse symlinks on command line only"
//usage: "\n -P Don't traverse symlinks (default)"
+//usage: )
+//next 4 options are the same for chmod/chown/chgrp:
+//usage: "\n -R Recurse"
+//usage: IF_DESKTOP(
//usage: "\n -c List changed files"
-//usage: "\n -v List all files"
+//usage: "\n -v Verbose"
//usage: "\n -f Hide errors"
//usage: )
//usage:
diff --git a/coreutils/cksum.c b/coreutils/cksum.c
index 633322bc7..83b7e3238 100644
--- a/coreutils/cksum.c
+++ b/coreutils/cksum.c
@@ -9,32 +9,40 @@
//config:config CKSUM
//config: bool "cksum (4.1 kb)"
//config: default y
-//config: help
-//config: cksum is used to calculate the CRC32 checksum of a file.
+//config:
+//config:config CRC32
+//config: bool "crc32 (4.1 kb)"
+//config: default y
+// APPLET_NOEXEC:name main location suid_type help
//applet:IF_CKSUM(APPLET_NOEXEC(cksum, cksum, BB_DIR_USR_BIN, BB_SUID_DROP, cksum))
+//applet:IF_CRC32(APPLET_NOEXEC(crc32, cksum, BB_DIR_USR_BIN, BB_SUID_DROP, cksum))
/* bb_common_bufsiz1 usage here is safe wrt NOEXEC: not expecting it to be zeroed. */
//kbuild:lib-$(CONFIG_CKSUM) += cksum.o
+//kbuild:lib-$(CONFIG_CRC32) += cksum.o
//usage:#define cksum_trivial_usage
//usage: "FILE..."
//usage:#define cksum_full_usage "\n\n"
-//usage: "Calculate the CRC32 checksums of FILEs"
+//usage: "Calculate CRC32 checksum of FILEs"
#include "libbb.h"
#include "common_bufsiz.h"
/* This is a NOEXEC applet. Be very careful! */
+#define IS_CKSUM (ENABLE_CKSUM && (!ENABLE_CRC32 || applet_name[1] == 'k'))
+#define IS_CRC32 (ENABLE_CRC32 && (!ENABLE_CKSUM || applet_name[1] == 'r'))
+
int cksum_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int cksum_main(int argc UNUSED_PARAM, char **argv)
{
- uint32_t *crc32_table = crc32_filltable(NULL, 1);
+ uint32_t *crc32_table = crc32_filltable(NULL, IS_CKSUM);
int exit_code = EXIT_SUCCESS;
#if ENABLE_DESKTOP
- getopt32(argv, ""); /* coreutils 6.9 compat */
+ getopt32(argv, ""); /* cksum coreutils 6.9 compat */
argv += optind;
#else
argv++;
@@ -43,41 +51,55 @@ int cksum_main(int argc UNUSED_PARAM, char **argv)
setup_common_bufsiz();
do {
uint32_t crc;
- off_t filesize;
- int fd = open_or_warn_stdin(*argv ? *argv : bb_msg_standard_input);
+ IF_CKSUM(off_t filesize;)
+ const char *fname = *argv ? *argv : bb_msg_standard_input;
+ int fd = open_or_warn_stdin(fname);
if (fd < 0) {
exit_code = EXIT_FAILURE;
continue;
}
- crc = 0;
- filesize = 0;
+ crc = IS_CKSUM ? 0 : 0xffffffff;
+ IF_CKSUM(filesize = 0;)
#define read_buf bb_common_bufsiz1
for (;;) {
- uoff_t t;
int bytes_read = safe_read(fd, read_buf, COMMON_BUFSIZE);
+ if (bytes_read < 0)
+ bb_simple_perror_msg_and_die(fname);
if (bytes_read > 0) {
- filesize += bytes_read;
+ IF_CKSUM(filesize += bytes_read;)
} else {
- /* Checksum filesize bytes, LSB first, and exit */
+ IF_CKSUM(uoff_t t;)
+
close(fd);
+ if (IS_CRC32)
+ break;
+#if ENABLE_CKSUM
fd = -1; /* break flag */
+ /* Checksum filesize bytes, LSB first */
t = filesize;
- bytes_read = 0;
+ /*bytes_read = 0; - already is */
while (t != 0) {
read_buf[bytes_read++] = (uint8_t)t;
t >>= 8;
}
+#endif
}
- crc = crc32_block_endian1(crc, read_buf, bytes_read, crc32_table);
- if (fd < 0)
+ crc = (IS_CKSUM ? crc32_block_endian1 : crc32_block_endian0)(crc, read_buf, bytes_read, crc32_table);
+ if (ENABLE_CKSUM && fd < 0)
break;
}
crc = ~crc;
- printf((*argv ? "%u %"OFF_FMT"u %s\n" : "%u %"OFF_FMT"u\n"),
+#if ENABLE_CKSUM
+ if (IS_CKSUM)
+ printf((*argv ? "%u %"OFF_FMT"u %s\n" : "%u %"OFF_FMT"u\n"),
(unsigned)crc, filesize, *argv);
+ else
+#endif
+ printf((*argv ? "%08x %s\n" : "%08x\n"),
+ (unsigned)crc, *argv);
} while (*argv && *++argv);
fflush_stdout_and_exit(exit_code);
diff --git a/coreutils/cp.c b/coreutils/cp.c
index f92ba6886..50ca1ccea 100644
--- a/coreutils/cp.c
+++ b/coreutils/cp.c
@@ -37,8 +37,55 @@
/* http://www.opengroup.org/onlinepubs/007904975/utilities/cp.html */
+// Options of cp from GNU coreutils 6.10:
+// -a, --archive
+// -f, --force
+// -i, --interactive
+// -l, --link
+// -L, --dereference
+// -P, --no-dereference
+// -R, -r, --recursive
+// -s, --symbolic-link
+// -v, --verbose
+// -H follow command-line symbolic links in SOURCE
+// -d same as --no-dereference --preserve=links
+// -p same as --preserve=mode,ownership,timestamps
+// -c same as --preserve=context
+// -u, --update
+// copy only when the SOURCE file is newer than the destination
+// file or when the destination file is missing
+// --remove-destination
+// remove each existing destination file before attempting to open
+// --parents
+// use full source file name under DIRECTORY
+// -T, --no-target-directory
+// treat DEST as a normal file
+// NOT SUPPORTED IN BBOX:
+// --backup[=CONTROL]
+// make a backup of each existing destination file
+// -b like --backup but does not accept an argument
+// --copy-contents
+// copy contents of special files when recursive
+// --preserve[=ATTR_LIST]
+// preserve attributes (default: mode,ownership,timestamps),
+// if possible additional attributes: security context,links,all
+// --no-preserve=ATTR_LIST
+// --sparse=WHEN
+// control creation of sparse files
+// --strip-trailing-slashes
+// remove any trailing slashes from each SOURCE argument
+// -S, --suffix=SUFFIX
+// override the usual backup suffix
+// -t, --target-directory=DIRECTORY
+// copy all SOURCE arguments into DIRECTORY
+// -x, --one-file-system
+// stay on this file system
+// -Z, --context=CONTEXT
+// (SELinux) set SELinux security context of copy to CONTEXT
+
//usage:#define cp_trivial_usage
-//usage: "[-arPLHpfilsTu] SOURCE... DEST"
+//usage: "[-arPLHpfinlsTu] SOURCE DEST\n"
+//usage: "or: cp [-arPLHpfinlsu] SOURCE... { -t DIRECTORY | DIRECTORY }"
//usage:#define cp_full_usage "\n\n"
//usage: "Copy SOURCEs to DEST\n"
//usage: "\n -a Same as -dpR"
@@ -52,8 +99,10 @@
//usage: "\n -p Preserve file attributes if possible"
//usage: "\n -f Overwrite"
//usage: "\n -i Prompt before overwrite"
+//usage: "\n -n Don't overwrite"
//usage: "\n -l,-s Create (sym)links"
-//usage: "\n -T Treat DEST as a normal file"
+//usage: "\n -T Refuse to copy if DEST is a directory"
+//usage: "\n -t DIR Copy all SOURCEs into DIR"
//usage: "\n -u Copy only newer files"
#include "libbb.h"
@@ -73,14 +122,12 @@ int cp_main(int argc, char **argv)
int flags;
int status;
enum {
- FILEUTILS_CP_OPTNUM = sizeof(FILEUTILS_CP_OPTSTR)-1,
#if ENABLE_FEATURE_CP_LONG_OPTIONS
- /*OPT_rmdest = FILEUTILS_RMDEST = 1 << FILEUTILS_CP_OPTNUM */
- OPT_parents = 1 << (FILEUTILS_CP_OPTNUM+1),
- OPT_reflink = 1 << (FILEUTILS_CP_OPTNUM+2),
+ /*OPT_rmdest = FILEUTILS_RMDEST = 1 << FILEUTILS_CP_OPTBITS */
+ OPT_parents = 1 << (FILEUTILS_CP_OPTBITS+1),
+ OPT_reflink = 1 << (FILEUTILS_CP_OPTBITS+2),
#endif
};
-
#if ENABLE_FEATURE_CP_LONG_OPTIONS
# if ENABLE_FEATURE_CP_REFLINK
char *reflink = NULL;
@@ -88,28 +135,34 @@ int cp_main(int argc, char **argv)
flags = getopt32long(argv, "^"
FILEUTILS_CP_OPTSTR
"\0"
- // Need at least two arguments
+ // Need at least one argument. (Usually two+, but -t DIR can have only one)
// Soft- and hardlinking doesn't mix
// -P and -d are the same (-P is POSIX, -d is GNU)
// -r and -R are the same
// -R (and therefore -r) turns on -d (coreutils does this)
// -a = -pdR
- "-2:l--s:s--l:Pd:rRd:Rd:apdR",
+ // -i overrides -n and vice versa (last wins)
+ "-1:l--s:s--l:Pd:rRd:Rd:apdR:i-n:n-i",
"archive\0" No_argument "a"
"force\0" No_argument "f"
"interactive\0" No_argument "i"
+ "no-clobber\0" No_argument "n"
"link\0" No_argument "l"
"dereference\0" No_argument "L"
"no-dereference\0" No_argument "P"
"recursive\0" No_argument "R"
"symbolic-link\0" No_argument "s"
"no-target-directory\0" No_argument "T"
+ "target-directory\0" Required_argument "t"
"verbose\0" No_argument "v"
"update\0" No_argument "u"
"remove-destination\0" No_argument "\xff"
"parents\0" No_argument "\xfe"
# if ENABLE_FEATURE_CP_REFLINK
"reflink\0" Optional_argument "\xfd"
+# endif
+ , &last
+# if ENABLE_FEATURE_CP_REFLINK
, &reflink
# endif
);
@@ -128,55 +181,10 @@ int cp_main(int argc, char **argv)
flags = getopt32(argv, "^"
FILEUTILS_CP_OPTSTR
"\0"
- "-2:l--s:s--l:Pd:rRd:Rd:apdR"
+ "-1:l--s:s--l:Pd:rRd:Rd:apdR"
+ , &last
);
#endif
- /* Options of cp from GNU coreutils 6.10:
- * -a, --archive
- * -f, --force
- * -i, --interactive
- * -l, --link
- * -L, --dereference
- * -P, --no-dereference
- * -R, -r, --recursive
- * -s, --symbolic-link
- * -v, --verbose
- * -H follow command-line symbolic links in SOURCE
- * -d same as --no-dereference --preserve=links
- * -p same as --preserve=mode,ownership,timestamps
- * -c same as --preserve=context
- * -u, --update
- * copy only when the SOURCE file is newer than the destination
- * file or when the destination file is missing
- * --remove-destination
- * remove each existing destination file before attempting to open
- * --parents
- * use full source file name under DIRECTORY
- * -T, --no-target-directory
- * treat DEST as a normal file
- * NOT SUPPORTED IN BBOX:
- * --backup[=CONTROL]
- * make a backup of each existing destination file
- * -b like --backup but does not accept an argument
- * --copy-contents
- * copy contents of special files when recursive
- * --preserve[=ATTR_LIST]
- * preserve attributes (default: mode,ownership,timestamps),
- * if possible additional attributes: security context,links,all
- * --no-preserve=ATTR_LIST
- * --sparse=WHEN
- * control creation of sparse files
- * --strip-trailing-slashes
- * remove any trailing slashes from each SOURCE argument
- * -S, --suffix=SUFFIX
- * override the usual backup suffix
- * -t, --target-directory=DIRECTORY
- * copy all SOURCE arguments into DIRECTORY
- * -x, --one-file-system
- * stay on this file system
- * -Z, --context=CONTEXT
- * (SELinux) set SELinux security context of copy to CONTEXT
- */
argc -= optind;
argv += optind;
/* Reverse this bit. If there is -d, bit is not set: */
@@ -195,49 +203,56 @@ int cp_main(int argc, char **argv)
#endif
status = EXIT_SUCCESS;
- last = argv[argc - 1];
- /* If there are only two arguments and... */
- if (argc == 2) {
- s_flags = cp_mv_stat2(*argv, &source_stat,
- (flags & FILEUTILS_DEREFERENCE) ? stat : lstat);
- if (s_flags < 0)
- return EXIT_FAILURE;
- d_flags = cp_mv_stat(last, &dest_stat);
- if (d_flags < 0)
- return EXIT_FAILURE;
+ if (!(flags & FILEUTILS_TARGET_DIR)) {
+ last = argv[argc - 1];
+ if (argc < 2)
+ bb_show_usage();
+ if (argc != 2) {
+ if (flags & FILEUTILS_NO_TARGET_DIR)
+ bb_show_usage();
+ /* "cp A B C... DIR" - target must be dir */
+ } else /* argc == 2 */ {
+ /* "cp A B" - only case where target can be not a dir */
+ s_flags = cp_mv_stat2(*argv, &source_stat,
+ (flags & FILEUTILS_DEREFERENCE) ? stat : lstat);
+ if (s_flags < 0) /* error other than ENOENT */
+ return EXIT_FAILURE;
+ d_flags = cp_mv_stat(last, &dest_stat);
+ if (d_flags < 0) /* error other than ENOENT */
+ return EXIT_FAILURE;
- if (flags & FILEUTILS_NO_TARGET_DIR) { /* -T */
- if (!(s_flags & 2) && (d_flags & 2))
- /* cp -T NOTDIR DIR */
- bb_error_msg_and_die("'%s' is a directory", last);
- }
+ if (flags & FILEUTILS_NO_TARGET_DIR) { /* -T */
+ if (!(s_flags & 2) && (d_flags & 2))
+ /* cp -T NOTDIR DIR */
+ bb_error_msg_and_die("'%s' is a directory", last);
+ }
#if ENABLE_FEATURE_CP_LONG_OPTIONS
- //bb_error_msg("flags:%x FILEUTILS_RMDEST:%x OPT_parents:%x",
- // flags, FILEUTILS_RMDEST, OPT_parents);
- if (flags & OPT_parents) {
- if (!(d_flags & 2)) {
- bb_simple_error_msg_and_die("with --parents, the destination must be a directory");
+ //bb_error_msg("flags:%x FILEUTILS_RMDEST:%x OPT_parents:%x",
+ // flags, FILEUTILS_RMDEST, OPT_parents);
+ if (flags & OPT_parents) {
+ if (!(d_flags & 2)) {
+ bb_simple_error_msg_and_die("with --parents, the destination must be a directory");
+ }
+ }
+ if (flags & FILEUTILS_RMDEST) {
+ flags |= FILEUTILS_FORCE;
}
- }
- if (flags & FILEUTILS_RMDEST) {
- flags |= FILEUTILS_FORCE;
- }
#endif
- /* ...if neither is a directory... */
- if (!((s_flags | d_flags) & 2)
- /* ...or: recursing, the 1st is a directory, and the 2nd doesn't exist... */
- || ((flags & FILEUTILS_RECUR) && (s_flags & 2) && !d_flags)
- || (flags & FILEUTILS_NO_TARGET_DIR)
- ) {
- /* Do a simple copy */
- dest = last;
- goto DO_COPY; /* NB: argc==2 -> *++argv==last */
+ /* ...if neither is a directory... */
+ if (!((s_flags | d_flags) & 2)
+ /* ...or: recursing, the 1st is a directory, and the 2nd doesn't exist... */
+ || ((flags & FILEUTILS_RECUR) && (s_flags & 2) && !d_flags)
+ || (flags & FILEUTILS_NO_TARGET_DIR)
+ ) {
+ /* Do a simple copy */
+ dest = last;
+ goto DO_COPY; /* NB: argc==2 -> *++argv==last */
+ }
}
- } else if (flags & FILEUTILS_NO_TARGET_DIR) {
- bb_simple_error_msg_and_die("too many arguments");
}
+ /* else: last is DIR from "-t DIR" */
while (1) {
#if ENABLE_FEATURE_CP_LONG_OPTIONS
@@ -259,7 +274,7 @@ int cp_main(int argc, char **argv)
if (copy_file(*argv, dest, flags) < 0) {
status = EXIT_FAILURE;
}
- if (*++argv == last) {
+ if (!*++argv || *argv == last) {
/* possibly leaking dest... */
break;
}
diff --git a/coreutils/cut.c b/coreutils/cut.c
index 5897d82b6..7009e74cf 100644
--- a/coreutils/cut.c
+++ b/coreutils/cut.c
@@ -14,6 +14,13 @@
//config: help
//config: cut is used to print selected parts of lines from
//config: each file to stdout.
+//config:
+//config:config FEATURE_CUT_REGEX
+//config: bool "cut -F"
+//config: default y
+//config: depends on CUT
+//config: help
+//config: Allow regex based delimiters.
//applet:IF_CUT(APPLET_NOEXEC(cut, cut, BB_DIR_USR_BIN, BB_SUID_DROP, cut))
@@ -25,10 +32,16 @@
//usage: "Print selected fields from FILEs to stdout\n"
//usage: "\n -b LIST Output only bytes from LIST"
//usage: "\n -c LIST Output only characters from LIST"
-//usage: "\n -d CHAR Use CHAR instead of tab as field delimiter"
+//usage: "\n -d SEP Field delimiter for input (default -f TAB, -F run of whitespace)"
+//usage: "\n -O SEP Field delimeter for output (default = -d for -f, one space for -F)"
+//usage: "\n -D Don't sort/collate sections or match -fF lines without delimeter"
+//usage: "\n -f LIST Print only these fields (-d is single char)"
+//usage: IF_FEATURE_CUT_REGEX(
+//usage: "\n -F LIST Print only these fields (-d is regex)"
+//usage: )
//usage: "\n -s Output only lines containing delimiter"
-//usage: "\n -f LIST Print only these fields"
//usage: "\n -n Ignored"
+//(manpage:-n with -b: don't split multibyte characters)
//usage:
//usage:#define cut_example_usage
//usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n"
@@ -38,38 +51,49 @@
#include "libbb.h"
+#if ENABLE_FEATURE_CUT_REGEX
+#include "xregex.h"
+#else
+#define regex_t int
+typedef struct { int rm_eo, rm_so; } regmatch_t;
+#define xregcomp(x, ...) *(x) = 0
+#define regexec(...) 0
+#endif
+
/* This is a NOEXEC applet. Be very careful! */
/* option vars */
-#define OPT_STR "b:c:f:d:sn"
+#define OPT_STR "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
#define CUT_OPT_BYTE_FLGS (1 << 0)
#define CUT_OPT_CHAR_FLGS (1 << 1)
#define CUT_OPT_FIELDS_FLGS (1 << 2)
#define CUT_OPT_DELIM_FLGS (1 << 3)
-#define CUT_OPT_SUPPRESS_FLGS (1 << 4)
+#define CUT_OPT_ODELIM_FLGS (1 << 4)
+#define CUT_OPT_SUPPRESS_FLGS (1 << 5)
+#define CUT_OPT_NOSORT_FLGS (1 << 6)
+#define CUT_OPT_REGEX_FLGS ((1 << 7) * ENABLE_FEATURE_CUT_REGEX)
struct cut_list {
int startpos;
int endpos;
};
-enum {
- BOL = 0,
- EOL = INT_MAX,
- NON_RANGE = -1
-};
-
static int cmpfunc(const void *a, const void *b)
{
return (((struct cut_list *) a)->startpos -
((struct cut_list *) b)->startpos);
}
-static void cut_file(FILE *file, char delim, const struct cut_list *cut_lists, unsigned nlists)
+static void cut_file(FILE *file, const char *delim, const char *odelim,
+ const struct cut_list *cut_lists, unsigned nlists)
{
char *line;
unsigned linenum = 0; /* keep these zero-based to be consistent */
+ regex_t reg;
+ int spos, shoe = option_mask32 & CUT_OPT_REGEX_FLGS;
+
+ if (shoe) xregcomp(&reg, delim, REG_EXTENDED);
/* go through every line in the file */
while ((line = xmalloc_fgetline(file)) != NULL) {
@@ -79,29 +103,22 @@ static void cut_file(FILE *file, char delim, const struct cut_list *cut_lists, u
char *printed = xzalloc(linelen + 1);
char *orig_line = line;
unsigned cl_pos = 0;
- int spos;
/* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) {
/* print the chars specified in each cut list */
for (; cl_pos < nlists; cl_pos++) {
- spos = cut_lists[cl_pos].startpos;
- while (spos < linelen) {
+ for (spos = cut_lists[cl_pos].startpos; spos < linelen;) {
if (!printed[spos]) {
printed[spos] = 'X';
putchar(line[spos]);
}
- spos++;
- if (spos > cut_lists[cl_pos].endpos
- /* NON_RANGE is -1, so if below is true,
- * the above was true too (spos is >= 0) */
- /* || cut_lists[cl_pos].endpos == NON_RANGE */
- ) {
+ if (++spos > cut_lists[cl_pos].endpos) {
break;
}
}
}
- } else if (delim == '\n') { /* cut by lines */
+ } else if (*delim == '\n') { /* cut by lines */
spos = cut_lists[cl_pos].startpos;
/* get out if we have no more lists to process or if the lines
@@ -114,9 +131,7 @@ static void cut_file(FILE *file, char delim, const struct cut_list *cut_lists, u
while (spos < (int)linenum) {
spos++;
/* go to the next list if we're at the end of this one */
- if (spos > cut_lists[cl_pos].endpos
- || cut_lists[cl_pos].endpos == NON_RANGE
- ) {
+ if (spos > cut_lists[cl_pos].endpos) {
cl_pos++;
/* get out if there's no more lists to process */
if (cl_pos >= nlists)
@@ -134,55 +149,56 @@ static void cut_file(FILE *file, char delim, const struct cut_list *cut_lists, u
puts(line);
goto next_line;
} else { /* cut by fields */
- int ndelim = -1; /* zero-based / one-based problem */
- int nfields_printed = 0;
- char *field = NULL;
- char delimiter[2];
-
- delimiter[0] = delim;
- delimiter[1] = 0;
-
- /* does this line contain any delimiters? */
- if (strchr(line, delim) == NULL) {
- if (!(option_mask32 & CUT_OPT_SUPPRESS_FLGS))
- puts(line);
- goto next_line;
- }
-
- /* process each list on this line, for as long as we've got
- * a line to process */
- for (; cl_pos < nlists && line; cl_pos++) {
- spos = cut_lists[cl_pos].startpos;
- do {
- /* find the field we're looking for */
- while (line && ndelim < spos) {
- field = strsep(&line, delimiter);
- ndelim++;
- }
-
- /* we found it, and it hasn't been printed yet */
- if (field && ndelim == spos && !printed[ndelim]) {
- /* if this isn't our first time through, we need to
- * print the delimiter after the last field that was
- * printed */
- if (nfields_printed > 0)
- putchar(delim);
- fputs_stdout(field);
- printed[ndelim] = 'X';
- nfields_printed++; /* shouldn't overflow.. */
+ unsigned uu = 0, start = 0, end = 0, out = 0;
+ int dcount = 0;
+
+ /* Loop through bytes, finding next delimiter */
+ for (;;) {
+ /* End of current range? */
+ if (end == linelen || dcount > cut_lists[cl_pos].endpos) {
+ if (++cl_pos >= nlists) break;
+ if (option_mask32 & CUT_OPT_NOSORT_FLGS)
+ start = dcount = uu = 0;
+ end = 0;
+ }
+ /* End of current line? */
+ if (uu == linelen) {
+ /* If we've seen no delimiters, check -s */
+ if (!cl_pos && !dcount && !shoe) {
+ if (option_mask32 & CUT_OPT_SUPPRESS_FLGS)
+ goto next_line;
+ } else if (dcount<cut_lists[cl_pos].startpos)
+ start = linelen;
+ end = linelen;
+ } else {
+ /* Find next delimiter */
+ if (shoe) {
+ regmatch_t rr = {-1, -1};
+
+ if (!regexec(&reg, line+uu, 1, &rr, REG_NOTBOL|REG_NOTEOL)) {
+ end = uu + rr.rm_so;
+ uu += rr.rm_eo;
+ } else {
+ uu = linelen;
+ continue;
+ }
+ } else if (line[end = uu++] != *delim)
+ continue;
+
+ /* Got delimiter. Loop if not yet within range. */
+ if (dcount++ < cut_lists[cl_pos].startpos) {
+ start = uu;
+ continue;
}
-
- spos++;
-
- /* keep going as long as we have a line to work with,
- * this is a list, and we're not at the end of that
- * list */
- } while (spos <= cut_lists[cl_pos].endpos && line
- && cut_lists[cl_pos].endpos != NON_RANGE);
+ }
+ if (end != start || !shoe)
+ printf("%s%.*s", out++ ? odelim : "", end-start, line + start);
+ start = uu;
+ if (!dcount)
+ break;
}
}
- /* if we printed anything at all, we need to finish it with a
- * newline cuz we were handed a chomped line */
+ /* if we printed anything, finish with newline */
putchar('\n');
next_line:
linenum++;
@@ -197,37 +213,35 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
/* growable array holding a series of lists */
struct cut_list *cut_lists = NULL;
unsigned nlists = 0; /* number of elements in above list */
- char delim = '\t'; /* delimiter, default is tab */
char *sopt, *ltok;
+ const char *delim = NULL;
+ const char *odelim = NULL;
unsigned opt;
+#define ARG "bcf"IF_FEATURE_CUT_REGEX("F")
opt = getopt32(argv, "^"
- OPT_STR
- "\0" "b--bcf:c--bcf:f--bcf",
- &sopt, &sopt, &sopt, &ltok
+ OPT_STR // = "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
+ "\0" "b--"ARG":c--"ARG":f--"ARG IF_FEATURE_CUT_REGEX("F--"ARG),
+ &sopt, &sopt, &sopt, &delim, &odelim IF_FEATURE_CUT_REGEX(, &sopt)
);
+ if (!delim || !*delim)
+ delim = (opt & CUT_OPT_REGEX_FLGS) ? "[[:space:]]+" : "\t";
+ if (!odelim) odelim = (opt & CUT_OPT_REGEX_FLGS) ? " " : delim;
+
// argc -= optind;
argv += optind;
- if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS)))
+ if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS | CUT_OPT_REGEX_FLGS)))
bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields");
- if (opt & CUT_OPT_DELIM_FLGS) {
- if (ltok[0] && ltok[1]) { /* more than 1 char? */
- bb_simple_error_msg_and_die("the delimiter must be a single character");
- }
- delim = ltok[0];
- }
-
/* non-field (char or byte) cutting has some special handling */
- if (!(opt & CUT_OPT_FIELDS_FLGS)) {
+ if (!(opt & (CUT_OPT_FIELDS_FLGS|CUT_OPT_REGEX_FLGS))) {
static const char _op_on_field[] ALIGN1 = " only when operating on fields";
if (opt & CUT_OPT_SUPPRESS_FLGS) {
bb_error_msg_and_die
- ("suppressing non-delimited lines makes sense%s",
- _op_on_field);
+ ("suppressing non-delimited lines makes sense%s", _op_on_field);
}
- if (delim != '\t') {
+ if (opt & CUT_OPT_DELIM_FLGS) {
bb_error_msg_and_die
("a delimiter may be specified%s", _op_on_field);
}
@@ -252,7 +266,7 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
/* get the start pos */
ntok = strsep(&ltok, "-");
if (!ntok[0]) {
- s = BOL;
+ s = 0;
} else {
s = xatoi_positive(ntok);
/* account for the fact that arrays are zero based, while
@@ -263,24 +277,23 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
/* get the end pos */
if (ltok == NULL) {
- e = NON_RANGE;
+ e = s;
} else if (!ltok[0]) {
- e = EOL;
+ e = INT_MAX;
} else {
e = xatoi_positive(ltok);
/* if the user specified and end position of 0,
* that means "til the end of the line" */
- if (e == 0)
- e = EOL;
+ if (!*ltok)
+ e = INT_MAX;
+ else if (e < s)
+ bb_error_msg_and_die("%d<%d", e, s);
e--; /* again, arrays are zero based, lines are 1 based */
- if (e == s)
- e = NON_RANGE;
}
/* add the new list */
cut_lists = xrealloc_vector(cut_lists, 4, nlists);
- /* NB: startpos is always >= 0,
- * while endpos may be = NON_RANGE (-1) */
+ /* NB: startpos is always >= 0 */
cut_lists[nlists].startpos = s;
cut_lists[nlists].endpos = e;
nlists++;
@@ -293,7 +306,8 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
/* now that the lists are parsed, we need to sort them to make life
* easier on us when it comes time to print the chars / fields / lines
*/
- qsort(cut_lists, nlists, sizeof(cut_lists[0]), cmpfunc);
+ if (!(opt & CUT_OPT_NOSORT_FLGS))
+ qsort(cut_lists, nlists, sizeof(cut_lists[0]), cmpfunc);
}
{
@@ -308,7 +322,7 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
retval = EXIT_FAILURE;
continue;
}
- cut_file(file, delim, cut_lists, nlists);
+ cut_file(file, delim, odelim, cut_lists, nlists);
fclose_if_not_stdin(file);
} while (*++argv);
diff --git a/coreutils/df.c b/coreutils/df.c
index debb86867..e8d4bc8f2 100644
--- a/coreutils/df.c
+++ b/coreutils/df.c
@@ -45,7 +45,7 @@
//usage: IF_FEATURE_HUMAN_READABLE("mh")
//usage: "T"
//usage: IF_FEATURE_DF_FANCY("ai] [-B SIZE")
-//usage: "] [FILESYSTEM]..."
+//usage: "] [-t TYPE] [FILESYSTEM]..."
//usage:#define df_full_usage "\n\n"
//usage: "Print filesystem usage statistics\n"
//usage: "\n -P POSIX output format"
@@ -55,6 +55,7 @@
//usage: "\n -h Human readable (e.g. 1K 243M 2G)"
//usage: )
//usage: "\n -T Print filesystem type"
+//usage: "\n -t TYPE Print only mounts of this type"
//usage: IF_FEATURE_DF_FANCY(
//usage: "\n -a Show all filesystems"
//usage: "\n -i Inodes"
@@ -97,24 +98,31 @@ int df_main(int argc UNUSED_PARAM, char **argv)
FILE *mount_table;
struct mntent *mount_entry;
struct statvfs s;
-
enum {
- OPT_KILO = (1 << 0),
- OPT_POSIX = (1 << 1),
- OPT_FSTYPE = (1 << 2),
- OPT_ALL = (1 << 3) * ENABLE_FEATURE_DF_FANCY,
- OPT_INODE = (1 << 4) * ENABLE_FEATURE_DF_FANCY,
- OPT_BSIZE = (1 << 5) * ENABLE_FEATURE_DF_FANCY,
- OPT_HUMAN = (1 << (3 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
- OPT_MEGA = (1 << (4 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
+ OPT_KILO = (1 << 0),
+ OPT_POSIX = (1 << 1),
+ OPT_FSTYPE = (1 << 2),
+ OPT_t = (1 << 3),
+ OPT_ALL = (1 << 4) * ENABLE_FEATURE_DF_FANCY,
+ OPT_INODE = (1 << 5) * ENABLE_FEATURE_DF_FANCY,
+ OPT_BSIZE = (1 << 6) * ENABLE_FEATURE_DF_FANCY,
+ OPT_HUMAN = (1 << (4 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
+ OPT_MEGA = (1 << (5 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
};
const char *disp_units_hdr = NULL;
- char *chp;
+ char *chp, *opt_t;
init_unicode();
+ /* From the manpage of df from coreutils-6.10:
+ * Disk space is shown in 1K blocks by default, unless the environment
+ * variable POSIXLY_CORRECT is set, in which case 512-byte blocks are used.
+ */
+ if (getenv("POSIXLY_CORRECT")) /* TODO - a new libbb function? */
+ df_disp_hr = 512;
+
opt = getopt32(argv, "^"
- "kPT"
+ "kPTt:"
IF_FEATURE_DF_FANCY("aiB:")
IF_FEATURE_HUMAN_READABLE("hm")
"\0"
@@ -123,6 +131,7 @@ int df_main(int argc UNUSED_PARAM, char **argv)
#elif ENABLE_FEATURE_HUMAN_READABLE
"k-m:m-k"
#endif
+ , &opt_t
IF_FEATURE_DF_FANCY(, &chp)
);
if (opt & OPT_MEGA)
@@ -142,13 +151,6 @@ int df_main(int argc UNUSED_PARAM, char **argv)
got_it: ;
}
- /* From the manpage of df from coreutils-6.10:
- * Disk space is shown in 1K blocks by default, unless the environment
- * variable POSIXLY_CORRECT is set, in which case 512-byte blocks are used.
- */
- if (getenv("POSIXLY_CORRECT")) /* TODO - a new libbb function? */
- df_disp_hr = 512;
-
if (opt & OPT_HUMAN) {
df_disp_hr = 0;
disp_units_hdr = " Size";
@@ -214,6 +216,11 @@ int df_main(int argc UNUSED_PARAM, char **argv)
mount_point = mount_entry->mnt_dir;
fs_type = mount_entry->mnt_type;
+ if (opt & OPT_t) {
+ if (strcmp(fs_type, opt_t) != 0)
+ continue;
+ }
+
if (statvfs(mount_point, &s) != 0) {
bb_simple_perror_msg(mount_point);
goto set_error;
diff --git a/coreutils/du.c b/coreutils/du.c
index c8aedb6ef..832dd7594 100644
--- a/coreutils/du.c
+++ b/coreutils/du.c
@@ -42,6 +42,7 @@
//usage:#define du_full_usage "\n\n"
//usage: "Summarize disk space used for FILEs (or directories)\n"
//usage: "\n -a Show file sizes too"
+//usage: "\n -b Apparent size (including holes)"
//usage: "\n -L Follow all symlinks"
//usage: "\n -H Follow symlinks on command line"
//usage: "\n -d N Limit output to directories (and files with -a) of depth < N"
@@ -84,8 +85,9 @@ enum {
OPT_d_maxdepth = (1 << 6),
OPT_l_hardlinks = (1 << 7),
OPT_c_total = (1 << 8),
- OPT_h_for_humans = (1 << 9),
- OPT_m_mbytes = (1 << 10),
+ OPT_b = (1 << 9),
+ OPT_h_for_humans = (1 << 10),
+ OPT_m_mbytes = (1 << 11),
};
struct globals {
@@ -109,7 +111,7 @@ static void print(unsigned long long size, const char *filename)
/* TODO - May not want to defer error checking here. */
#if ENABLE_FEATURE_HUMAN_READABLE
# if ENABLE_DESKTOP
- /* ~30 bytes of code for extra comtat:
+ /* ~30 bytes of code for extra compat:
* coreutils' du rounds sizes up:
* for example, 1025k file is shown as "2" by du -m.
* We round to nearest if human-readable [too hard to fix],
@@ -124,12 +126,16 @@ static void print(unsigned long long size, const char *filename)
* If G.disp_unit == 0, show one fractional
* and use suffixes
*/
- make_human_readable_str(size, 512, G.disp_unit),
+ make_human_readable_str(size, (option_mask32 & OPT_b) ? 1 : 512, G.disp_unit),
filename);
#else
if (G.disp_k) {
- size++;
- size >>= 1;
+ if (!(option_mask32 & OPT_b)) {
+ size++;
+ size >>= 1;
+ } else {
+ size >>= 10;
+ }
}
printf("%llu\t%s\n", size, filename);
#endif
@@ -155,7 +161,7 @@ static unsigned long long du(const char *filename)
}
}
- sum = statbuf.st_blocks;
+ sum = ((option_mask32 & OPT_b) ? statbuf.st_size : statbuf.st_blocks);
if (S_ISLNK(statbuf.st_mode)) {
if (G.slink_depth > G.du_depth) { /* -H or -L */
@@ -164,7 +170,7 @@ static unsigned long long du(const char *filename)
G.status = EXIT_FAILURE;
return 0;
}
- sum = statbuf.st_blocks;
+ sum = ((option_mask32 & OPT_b) ? statbuf.st_size : statbuf.st_blocks);
if (G.slink_depth == 1) {
/* Convert -H to -L */
G.slink_depth = INT_MAX;
@@ -241,11 +247,14 @@ int du_main(int argc UNUSED_PARAM, char **argv)
*/
#if ENABLE_FEATURE_HUMAN_READABLE
opt = getopt32(argv, "^"
- "aHkLsxd:+lchm"
+ "aHkLsxd:+lcbhm"
"\0" "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s",
&G.max_print_depth
);
argv += optind;
+ if (opt & OPT_b) {
+ G.disp_unit = 1;
+ }
if (opt & OPT_h_for_humans) {
G.disp_unit = 0;
}
@@ -257,16 +266,16 @@ int du_main(int argc UNUSED_PARAM, char **argv)
}
#else
opt = getopt32(argv, "^"
- "aHkLsxd:+lc"
+ "aHkLsxd:+lcb"
"\0" "H-L:L-H:s-d:d-s",
&G.max_print_depth
);
argv += optind;
-#if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
+# if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
if (opt & OPT_k_kbytes) {
G.disp_k = 1;
}
-#endif
+# endif
#endif
if (opt & OPT_H_follow_links) {
G.slink_depth = 1;
diff --git a/coreutils/echo.c b/coreutils/echo.c
index aab177cee..82f0358b6 100644
--- a/coreutils/echo.c
+++ b/coreutils/echo.c
@@ -43,10 +43,10 @@
//usage:#define echo_trivial_usage
//usage: IF_FEATURE_FANCY_ECHO("[-neE] ") "[ARG]..."
//usage:#define echo_full_usage "\n\n"
-//usage: "Print the specified ARGs to stdout"
+//usage: "Print ARGs to stdout"
//usage: IF_FEATURE_FANCY_ECHO( "\n"
-//usage: "\n -n Suppress trailing newline"
-//usage: "\n -e Interpret backslash escapes (i.e., \\t=tab)"
+//usage: "\n -n No trailing newline"
+//usage: "\n -e Interpret backslash escapes (\\t=tab etc)"
//usage: "\n -E Don't interpret backslash escapes (default)"
//usage: )
//usage:
diff --git a/coreutils/env.c b/coreutils/env.c
index c37c0c2df..a0ea4dd27 100644
--- a/coreutils/env.c
+++ b/coreutils/env.c
@@ -39,12 +39,15 @@
/* http://www.opengroup.org/onlinepubs/007904975/utilities/env.html */
//usage:#define env_trivial_usage
-//usage: "[-iu] [-] [name=value]... [PROG ARGS]"
+//usage: "[-i0] [-u NAME]... [-] [NAME=VALUE]... [PROG ARGS]"
+// The "-" can occur only once (unlike, say, -i): it terminates option processing,
+// so if it is followed by another "-" arg (or any option-looking arg),
+// that arg will be taken as PROG (or even as NAME=VALUE, example: "-z=QWE").
//usage:#define env_full_usage "\n\n"
-//usage: "Print the current environment or run PROG after setting up\n"
-//usage: "the specified environment\n"
-//usage: "\n -, -i Start with an empty environment"
-//usage: "\n -u Remove variable from the environment"
+//usage: "Print current environment or run PROG after setting up environment\n"
+//usage: "\n -, -i Start with empty environment"
+//usage: "\n -0 NUL terminated output"
+//usage: "\n -u NAME Remove variable from environment"
#include "libbb.h"
@@ -54,8 +57,9 @@ int env_main(int argc UNUSED_PARAM, char **argv)
unsigned opts;
llist_t *unset_env = NULL;
- opts = getopt32long(argv, "+iu:*",
+ opts = getopt32long(argv, "+i0u:*",
"ignore-environment\0" No_argument "i"
+ "null\0" No_argument "0"
"unset\0" Required_argument "u"
, &unset_env
);
@@ -90,8 +94,9 @@ int env_main(int argc UNUSED_PARAM, char **argv)
if (environ) { /* clearenv() may set environ == NULL! */
char **ep;
+ opts = (opts & 2) ? 0 : '\n';
for (ep = environ; *ep; ep++) {
- puts(*ep);
+ printf("%s%c", *ep, opts);
}
}
diff --git a/coreutils/expr.c b/coreutils/expr.c
index b247f08db..760b081f9 100644
--- a/coreutils/expr.c
+++ b/coreutils/expr.c
@@ -45,7 +45,7 @@
//usage:#define expr_trivial_usage
//usage: "EXPRESSION"
//usage:#define expr_full_usage "\n\n"
-//usage: "Print the value of EXPRESSION to stdout\n"
+//usage: "Print the value of EXPRESSION\n"
//usage: "\n"
//usage: "EXPRESSION may be:\n"
//usage: " ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n"
@@ -63,7 +63,7 @@
//usage: " ARG1 % ARG2\n"
//usage: " STRING : REGEXP Anchored pattern match of REGEXP in STRING\n"
//usage: " match STRING REGEXP Same as STRING : REGEXP\n"
-//usage: " substr STRING POS LENGTH Substring of STRING, POS counted from 1\n"
+//usage: " substr STRING POS LEN Substring of STRING, POS counts from 1\n"
//usage: " index STRING CHARS Index in STRING where any CHARS is found, or 0\n"
//usage: " length STRING Length of STRING\n"
//usage: " quote TOKEN Interpret TOKEN as a string, even if\n"
diff --git a/coreutils/head.c b/coreutils/head.c
index efb023c6f..9586f869f 100644
--- a/coreutils/head.c
+++ b/coreutils/head.c
@@ -29,17 +29,18 @@
//usage:#define head_trivial_usage
//usage: "[OPTIONS] [FILE]..."
//usage:#define head_full_usage "\n\n"
-//usage: "Print first 10 lines of FILEs (or stdin) to stdout.\n"
+//usage: "Print first 10 lines of FILEs (or stdin).\n"
//usage: "With more than one FILE, precede each with a filename header.\n"
-//usage: "\n -n N[kbm] Print first N lines"
+//usage: "\n -n N[bkm] Print first N lines"
+//usage: IF_FEATURE_FANCY_HEAD(
+//usage: "\n -n -N[bkm] Print all except N last lines"
+//usage: "\n -c [-]N[bkm] Print first N bytes"
+//usage: )
+//usage: "\n (b:*512 k:*1024 m:*1024^2)"
//usage: IF_FEATURE_FANCY_HEAD(
-//usage: "\n -n -N[kbm] Print all except N last lines"
-//usage: "\n -c [-]N[kbm] Print first N bytes"
//usage: "\n -q Never print headers"
//usage: "\n -v Always print headers"
//usage: )
-//usage: "\n"
-//usage: "\nN may be suffixed by k (x1024), b (x512), or m (x1024^2)."
//usage:
//usage:#define head_example_usage
//usage: "$ head -n 2 /etc/passwd\n"
diff --git a/coreutils/id.c b/coreutils/id.c
index f453a87ae..18bda3c55 100644
--- a/coreutils/id.c
+++ b/coreutils/id.c
@@ -52,7 +52,7 @@
//usage:#define groups_trivial_usage
//usage: "[USER]"
//usage:#define groups_full_usage "\n\n"
-//usage: "Print the group memberships of USER or for the current process"
+//usage: "Print the groups USER is in"
//usage:
//usage:#define groups_example_usage
//usage: "$ groups\n"
diff --git a/coreutils/ls.c b/coreutils/ls.c
index 80ef92079..9e8561606 100644
--- a/coreutils/ls.c
+++ b/coreutils/ls.c
@@ -109,11 +109,11 @@
//usage:#define ls_full_usage "\n\n"
//usage: "List directory contents\n"
//usage: "\n -1 One column output"
-//usage: "\n -a Include entries which start with ."
+//usage: "\n -a Include names starting with ."
//usage: "\n -A Like -a, but exclude . and .."
////usage: "\n -C List by columns" - don't show, this is a default anyway
//usage: "\n -x List by lines"
-//usage: "\n -d List directory entries instead of contents"
+//usage: "\n -d List directory names, not contents"
//usage: IF_FEATURE_LS_FOLLOWLINKS(
//usage: "\n -L Follow symlinks"
//usage: "\n -H Follow symlinks on command line"
@@ -122,10 +122,10 @@
//usage: "\n -R Recurse"
//usage: )
//usage: IF_FEATURE_LS_FILETYPES(
-//usage: "\n -p Append / to dir entries"
-//usage: "\n -F Append indicator (one of */=@|) to entries"
+//usage: "\n -p Append / to directory names"
+//usage: "\n -F Append indicator (one of */=@|) to names"
//usage: )
-//usage: "\n -l Long listing format"
+//usage: "\n -l Long format"
//usage: "\n -i List inode numbers"
//usage: "\n -n List numeric UIDs and GIDs instead of names"
//usage: "\n -s List allocated blocks"
@@ -134,7 +134,7 @@
//usage: "\n -lu List atime"
//usage: )
//usage: IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(
-//usage: "\n --full-time List full date and time"
+//usage: "\n --full-time List full date/time"
//usage: ))
//usage: IF_FEATURE_HUMAN_READABLE(
//usage: "\n -h Human readable sizes (1K 243M 2G)"
@@ -160,7 +160,7 @@
//usage: "\n -w N Format N columns wide"
//usage: )
//usage: IF_FEATURE_LS_COLOR(
-//usage: "\n --color[={always,never,auto}] Control coloring"
+//usage: "\n --color[={always,never,auto}]"
//usage: )
#include "libbb.h"
@@ -187,7 +187,7 @@
enum {
-TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
+TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
SPLIT_FILE = 0,
SPLIT_DIR = 1,
@@ -298,7 +298,7 @@ struct dnode {
// but there are invisible fields as well
// (such as nanosecond-resolution timespamps)
// and padding, which we also don't want to store.
-// We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
+// We also pre-parse dev_t dn_rdev (in glibc, it's huge).
// On 32-bit uclibc: dnode size went from 112 to 84 bytes.
//
/* Same names as in struct stat, but with dn_ instead of st_ pfx: */
@@ -1145,11 +1145,15 @@ int ls_main(int argc UNUSED_PARAM, char **argv)
#if ENABLE_FEATURE_LS_COLOR
/* set G_show_color = 1/0 */
- if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
+ if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && !is_TERM_dumb()) {
char *p = getenv("LS_COLORS");
/* LS_COLORS is unset, or (not empty && not "none") ? */
- if (!p || (p[0] && strcmp(p, "none") != 0))
- G_show_color = 1;
+ if (!p || (p[0] && strcmp(p, "none") != 0)) {
+ if (isatty(STDOUT_FILENO)) {
+ /* check isatty() last because it's expensive (syscall) */
+ G_show_color = 1;
+ }
+ }
}
if (opt & OPT_color) {
if (color_opt[0] == 'n')
@@ -1158,7 +1162,7 @@ int ls_main(int argc UNUSED_PARAM, char **argv)
case 3:
case 4:
case 5:
- if (isatty(STDOUT_FILENO)) {
+ if (!is_TERM_dumb() && isatty(STDOUT_FILENO)) {
case 0:
case 1:
case 2:
diff --git a/coreutils/mv.c b/coreutils/mv.c
index f5ed9fcfc..fd2422683 100644
--- a/coreutils/mv.c
+++ b/coreutils/mv.c
@@ -23,13 +23,15 @@
//kbuild:lib-$(CONFIG_MV) += mv.o
//usage:#define mv_trivial_usage
-//usage: "[-fin] SOURCE DEST\n"
-//usage: "or: mv [-fin] SOURCE... DIRECTORY"
+//usage: "[-finT] SOURCE DEST\n"
+//usage: "or: mv [-fin] SOURCE... { -t DIRECTORY | DIRECTORY }"
//usage:#define mv_full_usage "\n\n"
//usage: "Rename SOURCE to DEST, or move SOURCEs to DIRECTORY\n"
//usage: "\n -f Don't prompt before overwriting"
//usage: "\n -i Interactive, prompt before overwrite"
//usage: "\n -n Don't overwrite an existing file"
+//usage: "\n -T Refuse to move if DEST is a directory"
+//usage: "\n -t DIR Move all SOURCEs into DIR"
//usage:
//usage:#define mv_example_usage
//usage: "$ mv /tmp/foo /bin/bar\n"
@@ -40,7 +42,7 @@
int mv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int mv_main(int argc, char **argv)
{
- struct stat dest_stat;
+ struct stat statbuf;
const char *last;
const char *dest;
unsigned flags;
@@ -51,41 +53,66 @@ int mv_main(int argc, char **argv)
#define OPT_FORCE (1 << 0)
#define OPT_INTERACTIVE (1 << 1)
#define OPT_NOCLOBBER (1 << 2)
-#define OPT_VERBOSE ((1 << 3) * ENABLE_FEATURE_VERBOSE)
- /* Need at least two arguments.
- * If more than one of -f, -i, -n is specified , only the final one
- * takes effect (it unsets previous options).
- */
+#define OPT_DESTNOTDIR (1 << 3)
+#define OPT_DESTDIR (1 << 4)
+#define OPT_VERBOSE ((1 << 5) * ENABLE_FEATURE_VERBOSE)
flags = getopt32long(argv, "^"
- "finv"
+ "finTt:v"
"\0"
- "-2:f-in:i-fn:n-fi",
+ /* At least one argument. (Usually two+, but -t DIR can have only one) */
+ "-1"
+ /* only the final one of -f, -i, -n takes effect */
+ ":f-in:i-fn:n-fi"
+ /* -t and -T don't mix */
+ ":t--T:T--t",
"interactive\0" No_argument "i"
"force\0" No_argument "f"
"no-clobber\0" No_argument "n"
+ "no-target-directory\0" No_argument "T"
+ "target-directory\0" Required_argument "t"
IF_FEATURE_VERBOSE(
- "verbose\0" No_argument "v"
+ "verbose\0" No_argument "v",
+ &last
)
);
argc -= optind;
argv += optind;
- last = argv[argc - 1];
- if (argc == 2) {
- dest_exists = cp_mv_stat(last, &dest_stat);
- if (dest_exists < 0) {
- return EXIT_FAILURE;
- }
-
- if (!(dest_exists & 2)) { /* last is not a directory */
- dest = last;
- goto DO_MOVE;
+ if (!(flags & OPT_DESTDIR)) {
+ last = argv[argc - 1];
+ if (argc < 2)
+ bb_show_usage();
+ if (argc != 2) {
+ if (flags & OPT_DESTNOTDIR)
+ bb_show_usage();
+ /* "mv A B C... DIR" - target must be dir */
+ } else /* argc == 2 */ {
+ /* "mv A B" - only case where target can be not a dir */
+ dest_exists = cp_mv_stat(last, &statbuf);
+ if (dest_exists < 0) { /* error other than ENOENT */
+ return EXIT_FAILURE;
+ }
+ if (!(dest_exists & 2)) {
+ /* last is not a directory */
+ dest = last;
+ goto DO_MOVE;
+ }
+ /* last is a directory */
+ if (flags & OPT_DESTNOTDIR) {
+ if (stat(argv[0], &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+ bb_error_msg_and_die("'%s' is a directory", last);
+ /* "mv -T DIR1 DIR2" is allowed (renames a dir) */
+ dest = last;
+ goto DO_MOVE;
+ }
+ /* else: fall through into "do { move SRC to DIR/SRC } while" loop */
}
}
+ /* else: last is DIR from "-t DIR" */
do {
dest = concat_path_file(last, bb_get_last_path_component_strip(*argv));
- dest_exists = cp_mv_stat(dest, &dest_stat);
+ dest_exists = cp_mv_stat(dest, &statbuf);
if (dest_exists < 0) {
goto RET_1;
}
@@ -108,11 +135,10 @@ int mv_main(int argc, char **argv)
}
if (rename(*argv, dest) < 0) {
- struct stat source_stat;
int source_exists;
if (errno != EXDEV
- || (source_exists = cp_mv_stat2(*argv, &source_stat, lstat)) < 1
+ || (source_exists = cp_mv_stat2(*argv, &statbuf, lstat)) < 1
) {
bb_perror_msg("can't rename '%s'", *argv);
} else {
@@ -159,7 +185,7 @@ int mv_main(int argc, char **argv)
if (dest != last) {
free((void *) dest);
}
- } while (*++argv != last);
+ } while (*++argv && *argv != last);
return status;
}
diff --git a/coreutils/nproc.c b/coreutils/nproc.c
index 31e452bc4..bb9bc56c5 100644
--- a/coreutils/nproc.c
+++ b/coreutils/nproc.c
@@ -14,7 +14,7 @@
//kbuild:lib-$(CONFIG_NPROC) += nproc.o
//usage:#define nproc_trivial_usage
-//usage: ""IF_LONG_OPTS("--all --ignore=N")
+//usage: ""IF_LONG_OPTS("[--all] [--ignore=N]")
//usage:#define nproc_full_usage "\n\n"
//usage: "Print number of available CPUs"
//usage: IF_LONG_OPTS(
@@ -48,7 +48,7 @@ int nproc_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
if (cpuid && isdigit(cpuid[strlen(cpuid) - 1]))
count++;
}
- closedir(cpusd);
+ IF_FEATURE_CLEAN_UP(closedir(cpusd);)
}
} else
#endif
diff --git a/coreutils/shred.c b/coreutils/shred.c
index 8f3d9c5c9..04bf87229 100644
--- a/coreutils/shred.c
+++ b/coreutils/shred.c
@@ -15,14 +15,15 @@
//kbuild:lib-$(CONFIG_SHRED) += shred.o
//usage:#define shred_trivial_usage
-//usage: "FILE..."
+//usage: "[-fuz] [-n N] [-s SIZE] FILE..."
//usage:#define shred_full_usage "\n\n"
//usage: "Overwrite/delete FILEs\n"
//usage: "\n -f Chmod to ensure writability"
+//usage: "\n -s SIZE Size to write"
//usage: "\n -n N Overwrite N times (default 3)"
//usage: "\n -z Final overwrite with zeros"
//usage: "\n -u Remove file"
-//-x and -v are accepted but have no effect
+//-x (exact: don't round up to 4k) and -v (verbose) are accepted but have no effect
/* shred (GNU coreutils) 8.25:
-f, --force change permissions to allow writing if necessary
@@ -41,6 +42,7 @@
int shred_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int shred_main(int argc UNUSED_PARAM, char **argv)
{
+ char *opt_s;
int rand_fd = rand_fd; /* for compiler */
int zero_fd;
unsigned num_iter = 3;
@@ -52,18 +54,16 @@ int shred_main(int argc UNUSED_PARAM, char **argv)
OPT_n = (1 << 3),
OPT_v = (1 << 4),
OPT_x = (1 << 5),
+ OPT_s = (1 << 6),
};
- opt = getopt32(argv, "fuzn:+vx", &num_iter);
+ opt = getopt32(argv, "^" "fuzn:+vxs:" "\0" "-1"/*min 1 arg*/, &num_iter, &opt_s);
argv += optind;
zero_fd = xopen("/dev/zero", O_RDONLY);
if (num_iter != 0)
rand_fd = xopen("/dev/urandom", O_RDONLY);
- if (!*argv)
- bb_show_usage();
-
for (;;) {
struct stat sb;
const char *fname;
@@ -85,6 +85,11 @@ int shred_main(int argc UNUSED_PARAM, char **argv)
if (fstat(fd, &sb) == 0 && sb.st_size > 0) {
off_t size = sb.st_size;
+ if (opt & OPT_s) {
+ size = BB_STRTOOFF(opt_s, NULL, 0); /* accepts oct/hex */
+ if (errno || size < 0) bb_show_usage();
+ }
+
for (i = 0; i < num_iter; i++) {
bb_copyfd_size(rand_fd, fd, size);
fdatasync(fd);
@@ -94,12 +99,12 @@ int shred_main(int argc UNUSED_PARAM, char **argv)
bb_copyfd_size(zero_fd, fd, size);
fdatasync(fd);
}
- if (opt & OPT_u) {
- ftruncate(fd, 0);
- xunlink(fname);
- }
- xclose(fd);
}
+ if (opt & OPT_u) {
+ ftruncate(fd, 0);
+ xunlink(fname);
+ }
+ xclose(fd);
}
return EXIT_SUCCESS;
diff --git a/coreutils/sort.c b/coreutils/sort.c
index 6c4e3038c..32a06e40a 100644
--- a/coreutils/sort.c
+++ b/coreutils/sort.c
@@ -67,7 +67,7 @@
//usage: "\n -r Reverse sort order"
//usage: "\n -s Stable (don't sort ties alphabetically)"
//usage: "\n -u Suppress duplicate lines"
-//usage: "\n -z Lines are terminated by NUL, not newline"
+//usage: "\n -z NUL terminated input and output"
///////: "\n -m Ignored for GNU compatibility"
///////: "\n -S BUFSZ Ignored for GNU compatibility"
///////: "\n -T TMPDIR Ignored for GNU compatibility"
diff --git a/coreutils/tail.c b/coreutils/tail.c
index 08fde6cdd..6201eb023 100644
--- a/coreutils/tail.c
+++ b/coreutils/tail.c
@@ -48,19 +48,20 @@
//usage:#define tail_trivial_usage
//usage: "[OPTIONS] [FILE]..."
//usage:#define tail_full_usage "\n\n"
-//usage: "Print last 10 lines of FILEs (or stdin) to stdout.\n"
+//usage: "Print last 10 lines of FILEs (or stdin) to.\n"
//usage: "With more than one FILE, precede each with a filename header.\n"
-//usage: "\n -f Print data as file grows"
-//usage: "\n -c [+]N[kbm] Print last N bytes"
-//usage: "\n -n N[kbm] Print last N lines"
-//usage: "\n -n +N[kbm] Start on Nth line and print the rest"
+//usage: "\n -c [+]N[bkm] Print last N bytes"
+//usage: "\n -n N[bkm] Print last N lines"
+//usage: "\n -n +N[bkm] Start on Nth line and print the rest"
+//usage: "\n (b:*512 k:*1024 m:*1024^2)"
//usage: IF_FEATURE_FANCY_TAIL(
//usage: "\n -q Never print headers"
-//usage: "\n -s SECONDS Wait SECONDS between reads with -f"
//usage: "\n -v Always print headers"
+//usage: )
+//usage: "\n -f Print data as file grows"
+//usage: IF_FEATURE_FANCY_TAIL(
//usage: "\n -F Same as -f, but keep retrying"
-//usage: "\n"
-//usage: "\nN may be suffixed by k (x1024), b (x512), or m (x1024^2)."
+//usage: "\n -s SECONDS Wait SECONDS between reads with -f"
//usage: )
//usage:
//usage:#define tail_example_usage
@@ -118,7 +119,7 @@ int tail_main(int argc, char **argv)
char *tailbuf;
size_t tailbufsize;
- unsigned header_threshhold = 1;
+ unsigned header_threshold = 1;
unsigned nfiles;
int i, opt;
@@ -151,10 +152,10 @@ int tail_main(int argc, char **argv)
if (opt & 0x2) count = eat_num(str_c); // -c
if (opt & 0x4) count = eat_num(str_n); // -n
#if ENABLE_FEATURE_FANCY_TAIL
- /* q: make it impossible for nfiles to be > header_threshhold */
- if (opt & 0x8) header_threshhold = UINT_MAX; // -q
+ /* q: make it impossible for nfiles to be > header_threshold */
+ if (opt & 0x8) header_threshold = UINT_MAX; // -q
//if (opt & 0x10) // -s
- if (opt & 0x20) header_threshhold = 0; // -v
+ if (opt & 0x20) header_threshold = 0; // -v
# define FOLLOW_RETRY (opt & 0x40)
#else
# define FOLLOW_RETRY 0
@@ -215,7 +216,7 @@ int tail_main(int argc, char **argv)
if (ENABLE_FEATURE_FANCY_TAIL && fd < 0)
continue; /* may happen with -F */
- if (nfiles > header_threshhold) {
+ if (nfiles > header_threshold) {
tail_xprint_header(fmt, argv[i]);
fmt = header_fmt_str;
}
@@ -345,9 +346,11 @@ int tail_main(int argc, char **argv)
int nread;
const char *filename = argv[i];
int fd = fds[i];
+ int new_fd = -1;
+ struct stat sbuf;
if (FOLLOW_RETRY) {
- struct stat sbuf, fsbuf;
+ struct stat fsbuf;
if (fd < 0
|| fstat(fd, &fsbuf) < 0
@@ -355,39 +358,51 @@ int tail_main(int argc, char **argv)
|| fsbuf.st_dev != sbuf.st_dev
|| fsbuf.st_ino != sbuf.st_ino
) {
- int new_fd;
-
- if (fd >= 0)
- close(fd);
+ /* Looks like file has been created/renamed/deleted */
new_fd = open(filename, O_RDONLY);
if (new_fd >= 0) {
bb_error_msg("%s has %s; following end of new file",
filename, (fd < 0) ? "appeared" : "been replaced"
);
+ if (fd < 0) {
+ /* No previously open fd for this file,
+ * start using new_fd immediately. */
+ fds[i] = fd = new_fd;
+ new_fd = -1;
+ }
} else if (fd >= 0) {
- bb_perror_msg("%s has become inaccessible", filename);
+ bb_perror_msg("%s has been renamed or deleted", filename);
}
- fds[i] = fd = new_fd;
}
}
if (ENABLE_FEATURE_FANCY_TAIL && fd < 0)
continue;
- if (nfiles > header_threshhold) {
+ if (nfiles > header_threshold) {
fmt = header_fmt_str;
}
for (;;) {
/* tail -f keeps following files even if they are truncated */
- struct stat sbuf;
/* /proc files report zero st_size, don't lseek them */
- if (fstat(fd, &sbuf) == 0 && sbuf.st_size > 0) {
+ if (fstat(fd, &sbuf) == 0
+ /* && S_ISREG(sbuf.st_mode) TODO? */
+ && sbuf.st_size > 0
+ ) {
off_t current = lseek(fd, 0, SEEK_CUR);
- if (sbuf.st_size < current)
+ if (sbuf.st_size < current) {
+ //bb_perror_msg("%s: file truncated", filename); - says coreutils 8.32
xlseek(fd, 0, SEEK_SET);
+ }
}
nread = tail_read(fd, tailbuf, BUFSIZ);
- if (nread <= 0)
- break;
+ if (nread <= 0) {
+ if (new_fd < 0)
+ break;
+ /* Switch to "tail -F"ing the new file */
+ xmove_fd(new_fd, fd);
+ new_fd = -1;
+ continue;
+ }
if (fmt && (fd != prev_fd)) {
tail_xprint_header(fmt, filename);
fmt = NULL;
diff --git a/coreutils/touch.c b/coreutils/touch.c
index 2b225dd16..ec12eb7cf 100644
--- a/coreutils/touch.c
+++ b/coreutils/touch.c
@@ -31,7 +31,7 @@
//kbuild:lib-$(CONFIG_TOUCH) += touch.o
//usage:#define touch_trivial_usage
-//usage: "[-c" IF_FEATURE_TOUCH_SUSV3("am") "]"
+//usage: "[-ch" IF_FEATURE_TOUCH_SUSV3("am") "]"
//usage: IF_FEATURE_TOUCH_SUSV3(" [-d DATE] [-t DATE] [-r FILE]")
//usage: " FILE..."
//usage:#define touch_full_usage "\n\n"
diff --git a/coreutils/tty.c b/coreutils/tty.c
index ff6f2bb3b..e448c27ec 100644
--- a/coreutils/tty.c
+++ b/coreutils/tty.c
@@ -21,7 +21,7 @@
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/tty.html */
//usage:#define tty_trivial_usage
-//usage: ""
+//usage: "" IF_INCLUDE_SUSv2("[-s]")
//usage:#define tty_full_usage "\n\n"
//usage: "Print file name of stdin's terminal"
//usage: IF_INCLUDE_SUSv2( "\n"
diff --git a/coreutils/uname.c b/coreutils/uname.c
index 2a1602b4c..da785ab4c 100644
--- a/coreutils/uname.c
+++ b/coreutils/uname.c
@@ -79,13 +79,13 @@
//usage:#define uname_full_usage "\n\n"
//usage: "Print system information\n"
//usage: "\n -a Print all"
-//usage: "\n -m The machine (hardware) type"
+//usage: "\n -m Machine (hardware) type"
//usage: "\n -n Hostname"
//usage: "\n -r Kernel release"
//usage: "\n -s Kernel name (default)"
//usage: "\n -p Processor type"
//usage: "\n -v Kernel version"
-//usage: "\n -i The hardware platform"
+//usage: "\n -i Hardware platform"
//usage: "\n -o OS name"
//usage:
//usage:#define uname_example_usage
diff --git a/coreutils/uniq.c b/coreutils/uniq.c
index e1594286f..a3058ac07 100644
--- a/coreutils/uniq.c
+++ b/coreutils/uniq.c
@@ -20,13 +20,14 @@
/* http://www.opengroup.org/onlinepubs/007904975/utilities/uniq.html */
//usage:#define uniq_trivial_usage
-//usage: "[-cdui] [-f,s,w N] [INPUT [OUTPUT]]"
+//usage: "[-cduiz] [-f,s,w N] [FILE [OUTFILE]]"
//usage:#define uniq_full_usage "\n\n"
//usage: "Discard duplicate lines\n"
//usage: "\n -c Prefix lines by the number of occurrences"
//usage: "\n -d Only print duplicate lines"
//usage: "\n -u Only print unique lines"
//usage: "\n -i Ignore case"
+//usage: "\n -z NUL terminated output"
//usage: "\n -f N Skip first N fields"
//usage: "\n -s N Skip first N chars (after any skipped fields)"
//usage: "\n -w N Compare N characters in line"
@@ -45,23 +46,25 @@ int uniq_main(int argc UNUSED_PARAM, char **argv)
const char *input_filename;
unsigned skip_fields, skip_chars, max_chars;
unsigned opt;
+ char eol;
char *cur_line;
const char *cur_compare;
enum {
- OPT_c = 0x1,
- OPT_d = 0x2, /* print only dups */
- OPT_u = 0x4, /* print only uniq */
- OPT_f = 0x8,
- OPT_s = 0x10,
- OPT_w = 0x20,
- OPT_i = 0x40,
+ OPT_c = 1 << 0,
+ OPT_d = 1 << 1, /* print only dups */
+ OPT_u = 1 << 2, /* print only uniq */
+ OPT_f = 1 << 3,
+ OPT_s = 1 << 4,
+ OPT_w = 1 << 5,
+ OPT_i = 1 << 6,
+ OPT_z = 1 << 7,
};
skip_fields = skip_chars = 0;
max_chars = INT_MAX;
- opt = getopt32(argv, "cduf:+s:+w:+i", &skip_fields, &skip_chars, &max_chars);
+ opt = getopt32(argv, "cduf:+s:+w:+iz", &skip_fields, &skip_chars, &max_chars);
argv += optind;
input_filename = argv[0];
@@ -86,6 +89,7 @@ int uniq_main(int argc UNUSED_PARAM, char **argv)
}
cur_compare = cur_line = NULL; /* prime the pump */
+ eol = (opt & OPT_z) ? 0 : '\n';
do {
unsigned i;
@@ -127,7 +131,7 @@ int uniq_main(int argc UNUSED_PARAM, char **argv)
/* %7lu matches GNU coreutils 6.9 */
printf("%7lu ", dups + 1);
}
- puts(old_line);
+ printf("%s%c", old_line, eol);
}
free(old_line);
}
diff --git a/coreutils/uudecode.c b/coreutils/uudecode.c
index 02b037276..a607977e9 100644
--- a/coreutils/uudecode.c
+++ b/coreutils/uudecode.c
@@ -183,7 +183,7 @@ int uudecode_main(int argc UNUSED_PARAM, char **argv)
//usage:#define base32_trivial_usage
//usage: "[-d] [-w COL] [FILE]"
//usage:#define base32_full_usage "\n\n"
-//usage: "Base32 encode or decode FILE to standard output"
+//usage: "Base32 encode or decode FILE to standard output\n"
//usage: "\n -d Decode data"
//usage: "\n -w COL Wrap lines at COL (default 76, 0 disables)"
////usage: "\n -i When decoding, ignore non-alphabet characters"
@@ -191,10 +191,12 @@ int uudecode_main(int argc UNUSED_PARAM, char **argv)
//usage:#define base64_trivial_usage
//usage: "[-d] [-w COL] [FILE]"
//usage:#define base64_full_usage "\n\n"
-//usage: "Base64 encode or decode FILE to standard output"
+//usage: "Base64 encode or decode FILE to standard output\n"
//usage: "\n -d Decode data"
//usage: "\n -w COL Wrap lines at COL (default 76, 0 disables)"
-////usage: "\n -i When decoding, ignore non-alphabet characters"
+///////: "\n -i When decoding, ignore non-alphabet characters"
+// -i is accepted but has no effect: currently, decode_base32/64() functions
+// (called via read_base64()) skip invalid chars unconditionally.
// APPLET_ODDNAME:name main location suid_type help
//applet:IF_BASE32(APPLET_ODDNAME(base32, baseNUM, BB_DIR_BIN, BB_SUID_DROP, base32))
@@ -272,7 +274,7 @@ int baseNUM_main(int argc UNUSED_PARAM, char **argv)
unsigned opts;
unsigned col = 76;
- opts = getopt32(argv, "^" "dw:+" "\0" "?1"/* 1 arg max*/, &col);
+ opts = getopt32(argv, "^" "diw:+" "\0" "?1"/* 1 arg max*/, &col);
argv += optind;
if (!argv[0])
diff --git a/coreutils/who.c b/coreutils/who.c
index be9c3ccca..3725d77f5 100644
--- a/coreutils/who.c
+++ b/coreutils/who.c
@@ -78,7 +78,7 @@
// root pts/1 Mon13 3:24m 1:01 0.01s w
//usage:#define who_trivial_usage
-//usage: "[-a]"
+//usage: "[-aH]"
//usage:#define who_full_usage "\n\n"
//usage: "Show who is logged on\n"
//usage: "\n -a Show all"
diff --git a/coreutils/yes.c b/coreutils/yes.c
index 0ad25926f..161db82c0 100644
--- a/coreutils/yes.c
+++ b/coreutils/yes.c
@@ -27,7 +27,7 @@
//usage:#define yes_trivial_usage
//usage: "[STRING]"
//usage:#define yes_full_usage "\n\n"
-//usage: "Repeatedly output a line with STRING, or 'y'"
+//usage: "Repeatedly print a line with STRING, or 'y'"
#include "libbb.h"
diff --git a/debianutils/run_parts.c b/debianutils/run_parts.c
index 585a4b58f..f528c88ff 100644
--- a/debianutils/run_parts.c
+++ b/debianutils/run_parts.c
@@ -113,13 +113,24 @@ enum {
};
/* Is this a valid filename (upper/lower alpha, digits,
- * underscores, and hyphens only?)
+ * underscores, hyphens, and non-leading dots only?)
*/
static bool invalid_name(const char *c)
{
c = bb_basename(c);
- while (*c && (isalnum(*c) || *c == '_' || *c == '-'))
+ if (*c == '.')
+ return *c;
+
+ /* Debian run-parts 4.8.3, manpage:
+ * "...the names must consist entirely of ASCII letters,
+ * ASCII digits, ASCII underscores, and ASCII minus-hyphens.
+ * However, the name must not begin with a period."
+ * The last sentence is a giveaway that something is fishy
+ * (why mention leading dot if dots are not allowed anyway?).
+ * Yes, you guessed it right: in fact non-leading dots ARE allowed.
+ */
+ while (isalnum(*c) || *c == '_' || *c == '-' || *c == '.')
c++;
return *c; /* TRUE (!0) if terminating NUL is not reached */
diff --git a/docs/busybox_footer.pod b/docs/busybox_footer.pod
index 92748eb72..0f4810bd3 100644
--- a/docs/busybox_footer.pod
+++ b/docs/busybox_footer.pod
@@ -37,6 +37,7 @@ incorrect, please send in an update.
=for html <br>
Emanuele Aina <emanuele.aina@tiscali.it>
+
run-parts
=for html <br>
diff --git a/e2fsprogs/chattr.c b/e2fsprogs/chattr.c
index c37469021..f436cd91e 100644
--- a/e2fsprogs/chattr.c
+++ b/e2fsprogs/chattr.c
@@ -20,40 +20,52 @@
//kbuild:lib-$(CONFIG_CHATTR) += chattr.o e2fs_lib.o
//usage:#define chattr_trivial_usage
-//usage: "[-R] [-v VERSION] [-+=AacDdijsStTu] FILE..."
+//usage: "[-R] [-v VERSION] [-p PROJID] [-+=AacDdijsStTu] FILE..."
//usage:#define chattr_full_usage "\n\n"
//usage: "Change ext2 file attributes\n"
//usage: "\n -R Recurse"
-//usage: "\n -v VER Set version/generation number"
+//usage: "\n -v NUM Set version/generation number"
+//usage: "\n -p NUM Set project number"
//-V, -f accepted but ignored
//usage: "\nModifiers:"
//usage: "\n -,+,= Remove/add/set attributes"
//usage: "\nAttributes:"
-//usage: "\n A Don't track atime"
-//usage: "\n a Append mode only"
-//usage: "\n c Enable compress"
-//usage: "\n D Write dir contents synchronously"
+//usage: "\n A No atime"
+//usage: "\n a Append only"
+//usage: "\n C No copy-on-write"
+//usage: "\n c Compressed"
+//usage: "\n D Synchronous dir updates"
//usage: "\n d Don't backup with dump"
-//usage: "\n i Cannot be modified (immutable)"
-//usage: "\n j Write all data to journal first"
-//usage: "\n s Zero disk storage when deleted"
-//usage: "\n S Write synchronously"
-//usage: "\n t Disable tail-merging of partial blocks with other files"
-//usage: "\n u Allow file to be undeleted"
+//usage: "\n E Encrypted"
+//usage: "\n e File uses extents"
+//usage: "\n F Case-insensitive dir"
+//usage: "\n I Indexed dir"
+//usage: "\n i Immutable"
+//usage: "\n j Write data to journal first"
+//usage: "\n N File is stored in inode"
+//usage: "\n P Hierarchical project ID dir"
+//usage: "\n S Synchronous file updates"
+//usage: "\n s Zero storage when deleted"
+//usage: "\n T Top of dir hierarchy"
+//usage: "\n t Don't tail-merge with other files"
+//usage: "\n u Allow undelete"
+//usage: "\n V Verity"
#include "libbb.h"
#include "e2fs_lib.h"
-#define OPT_ADD 1
-#define OPT_REM 2
-#define OPT_SET 4
-#define OPT_SET_VER 8
+#define OPT_ADD (1 << 0)
+#define OPT_REM (1 << 1)
+#define OPT_SET (1 << 2)
+#define OPT_SET_VER (1 << 3)
+#define OPT_SET_PROJ (1 << 4)
struct globals {
- unsigned long version;
- unsigned long af;
- unsigned long rf;
+ unsigned version;
+ unsigned af;
+ unsigned rf;
int flags;
+ uint32_t projid;
smallint recursive;
};
@@ -67,13 +79,15 @@ static unsigned long get_flag(char c)
static char** decode_arg(char **argv, struct globals *gp)
{
- unsigned long *fl;
+ unsigned *fl;
const char *arg = *argv;
char opt = *arg;
fl = &gp->af;
if (opt == '-') {
- gp->flags |= OPT_REM;
+ /* gp->flags |= OPT_REM; - WRONG, it can be an option */
+ /* testcase: chattr =ae -R FILE should not complain "= is incompatible with - and +" */
+ /* (and should not read flags, with =FLAGS they can be just set directly) */
fl = &gp->rf;
} else if (opt == '+') {
gp->flags |= OPT_ADD;
@@ -103,14 +117,21 @@ static char** decode_arg(char **argv, struct globals *gp)
if (*arg == 'v') {
if (!*++argv)
bb_show_usage();
- gp->version = xatoul(*argv);
+ gp->version = xatou(*argv);
gp->flags |= OPT_SET_VER;
continue;
}
-//TODO: "-p PROJECT_NUM" ?
+ if (*arg == 'p') {
+ if (!*++argv)
+ bb_show_usage();
+ gp->projid = xatou32(*argv);
+ gp->flags |= OPT_SET_PROJ;
+ continue;
+ }
/* not a known option, try as an attribute */
+ gp->flags |= OPT_REM;
}
- *fl |= get_flag(*arg);
+ *fl |= get_flag(*arg); /* aborts on bad flag letter */
}
return argv;
@@ -120,6 +141,8 @@ static void change_attributes(const char *name, struct globals *gp);
static int FAST_FUNC chattr_dir_proc(const char *dir_name, struct dirent *de, void *gp)
{
+//TODO: use de->d_type (if it's not DT_UNKNOWN) to skip !(REG || DIR || LNK) entries without lstat?
+
char *path = concat_subpath_file(dir_name, de->d_name);
/* path is NULL if de->d_name is "." or "..", else... */
if (path) {
@@ -131,15 +154,14 @@ static int FAST_FUNC chattr_dir_proc(const char *dir_name, struct dirent *de, vo
static void change_attributes(const char *name, struct globals *gp)
{
- unsigned long fsflags;
+ unsigned fsflags;
+ int fd;
struct stat st;
if (lstat(name, &st) != 0) {
- bb_perror_msg("stat %s", name);
+ bb_perror_msg("can't stat '%s'", name);
return;
}
- if (S_ISLNK(st.st_mode) && gp->recursive)
- return;
/* Don't try to open device files, fifos etc. We probably
* ought to display an error if the file was explicitly given
@@ -148,29 +170,58 @@ static void change_attributes(const char *name, struct globals *gp)
if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode))
return;
- if (gp->flags & OPT_SET_VER)
- if (fsetversion(name, gp->version) != 0)
- bb_perror_msg("setting version on %s", name);
+ /* There is no way to run needed ioctls on a symlink.
+ * open(O_PATH | O_NOFOLLOW) _can_ be used to get a fd referring to the symlink,
+ * but ioctls fail on such a fd (tried on 4.12.0 kernel).
+ * e2fsprogs-1.46.2 uses open(O_NOFOLLOW), it fails on symlinks.
+ */
+ fd = open_or_warn(name, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_NOFOLLOW);
+ if (fd >= 0) {
+ int r;
+
+ if (gp->flags & OPT_SET_VER) {
+ r = ioctl(fd, EXT2_IOC_SETVERSION, &gp->version);
+ if (r != 0)
+ bb_perror_msg("setting %s on %s", "version", name);
+ }
- if (gp->flags & OPT_SET) {
- fsflags = gp->af;
- } else {
- if (fgetflags(name, &fsflags) != 0) {
- bb_perror_msg("reading flags on %s", name);
- goto skip_setflags;
+ if (gp->flags & OPT_SET_PROJ) {
+ struct ext2_fsxattr fsxattr;
+ r = ioctl(fd, EXT2_IOC_FSGETXATTR, &fsxattr);
+ /* note: ^^^ may fail in 32-bit userspace on 64-bit kernel (seen on 4.12.0) */
+ if (r != 0) {
+ bb_perror_msg("getting %s on %s", "project ID", name);
+ } else {
+ fsxattr.fsx_projid = gp->projid;
+ r = ioctl(fd, EXT2_IOC_FSSETXATTR, &fsxattr);
+ if (r != 0)
+ bb_perror_msg("setting %s on %s", "project ID", name);
+ }
}
- /*if (gp->flags & OPT_REM) - not needed, rf is zero otherwise */
- fsflags &= ~gp->rf;
- /*if (gp->flags & OPT_ADD) - not needed, af is zero otherwise */
- fsflags |= gp->af;
+
+ if (gp->flags & OPT_SET) {
+ fsflags = gp->af;
+ } else {
+ r = ioctl(fd, EXT2_IOC_GETFLAGS, &fsflags);
+ if (r != 0) {
+ bb_perror_msg("getting %s on %s", "flags", name);
+ goto skip_setflags;
+ }
+ /*if (gp->flags & OPT_REM) - not needed, rf is zero otherwise */
+ fsflags &= ~gp->rf;
+ /*if (gp->flags & OPT_ADD) - not needed, af is zero otherwise */
+ fsflags |= gp->af;
// What is this? And why it's not done for SET case?
- if (!S_ISDIR(st.st_mode))
- fsflags &= ~EXT2_DIRSYNC_FL;
+ if (!S_ISDIR(st.st_mode))
+ fsflags &= ~EXT2_DIRSYNC_FL;
+ }
+ r = ioctl(fd, EXT2_IOC_SETFLAGS, &fsflags);
+ if (r != 0)
+ bb_perror_msg("setting %s on %s", "flags", name);
+ skip_setflags:
+ close(fd);
}
- if (fsetflags(name, fsflags) != 0)
- bb_perror_msg("setting flags on %s", name);
- skip_setflags:
if (gp->recursive && S_ISDIR(st.st_mode))
iterate_on_dir(name, chattr_dir_proc, gp);
}
@@ -200,7 +251,7 @@ int chattr_main(int argc UNUSED_PARAM, char **argv)
if (g.rf & g.af)
bb_simple_error_msg_and_die("can't set and unset a flag");
if (!g.flags)
- bb_simple_error_msg_and_die("must use '-v', =, - or +");
+ bb_simple_error_msg_and_die("must use -v, -p, =, - or +");
/* now run chattr on all the files passed to us */
do change_attributes(*argv, &g); while (*++argv);
diff --git a/e2fsprogs/e2fs_lib.c b/e2fsprogs/e2fs_lib.c
index 8bd4da622..9b68d8901 100644
--- a/e2fsprogs/e2fs_lib.c
+++ b/e2fsprogs/e2fs_lib.c
@@ -8,133 +8,13 @@
#include "libbb.h"
#include "e2fs_lib.h"
-#define HAVE_EXT2_IOCTLS 1
-
-#if INT_MAX == LONG_MAX
-#define IF_LONG_IS_SAME(...) __VA_ARGS__
-#define IF_LONG_IS_WIDER(...)
-#else
-#define IF_LONG_IS_SAME(...)
-#define IF_LONG_IS_WIDER(...) __VA_ARGS__
-#endif
-
-static void close_silently(int fd)
-{
- int e = errno;
- close(fd);
- errno = e;
-}
-
-
-/* Iterate a function on each entry of a directory */
-int iterate_on_dir(const char *dir_name,
- int FAST_FUNC (*func)(const char *, struct dirent *, void *),
- void *private)
-{
- DIR *dir;
- struct dirent *de;
-
- dir = opendir(dir_name);
- if (dir == NULL) {
- return -1;
- }
- while ((de = readdir(dir)) != NULL) {
- func(dir_name, de, private);
- }
- closedir(dir);
- return 0;
-}
-
-
-/* Get/set a file version on an ext2 file system */
-int fgetsetversion(const char *name, unsigned long *get_version, unsigned long set_version)
-{
-#if HAVE_EXT2_IOCTLS
- int fd, r;
- IF_LONG_IS_WIDER(int ver;)
-
- fd = open(name, O_RDONLY | O_NONBLOCK);
- if (fd == -1)
- return -1;
- if (!get_version) {
- IF_LONG_IS_WIDER(
- ver = (int) set_version;
- r = ioctl(fd, EXT2_IOC_SETVERSION, &ver);
- )
- IF_LONG_IS_SAME(
- r = ioctl(fd, EXT2_IOC_SETVERSION, (void*)&set_version);
- )
- } else {
- IF_LONG_IS_WIDER(
- r = ioctl(fd, EXT2_IOC_GETVERSION, &ver);
- *get_version = ver;
- )
- IF_LONG_IS_SAME(
- r = ioctl(fd, EXT2_IOC_GETVERSION, (void*)get_version);
- )
- }
- close_silently(fd);
- return r;
-#else /* ! HAVE_EXT2_IOCTLS */
- errno = EOPNOTSUPP;
- return -1;
-#endif /* ! HAVE_EXT2_IOCTLS */
-}
-
-
-/* Get/set a file flags on an ext2 file system */
-int fgetsetflags(const char *name, unsigned long *get_flags, unsigned long set_flags)
-{
-#if HAVE_EXT2_IOCTLS
- struct stat buf;
- int fd, r;
- IF_LONG_IS_WIDER(int f;)
-
- if (stat(name, &buf) == 0 /* stat is ok */
- && !S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)
- ) {
- goto notsupp;
- }
- fd = open(name, O_RDONLY | O_NONBLOCK); /* neither read nor write asked for */
- if (fd == -1)
- return -1;
-
- if (!get_flags) {
- IF_LONG_IS_WIDER(
- f = (int) set_flags;
- r = ioctl(fd, EXT2_IOC_SETFLAGS, &f);
- )
- IF_LONG_IS_SAME(
- r = ioctl(fd, EXT2_IOC_SETFLAGS, (void*)&set_flags);
- )
- } else {
- IF_LONG_IS_WIDER(
- r = ioctl(fd, EXT2_IOC_GETFLAGS, &f);
- *get_flags = f;
- )
- IF_LONG_IS_SAME(
- r = ioctl(fd, EXT2_IOC_GETFLAGS, (void*)get_flags);
- )
- }
-
- close_silently(fd);
- return r;
- notsupp:
-#endif /* HAVE_EXT2_IOCTLS */
- errno = EOPNOTSUPP;
- return -1;
-}
-
-
/* Print file attributes on an ext2 file system */
const uint32_t e2attr_flags_value[] ALIGN4 = {
#ifdef ENABLE_COMPRESSION
EXT2_COMPRBLK_FL,
EXT2_DIRTY_FL,
EXT2_NOCOMPR_FL,
- EXT2_ECOMPR_FL,
#endif
- EXT2_INDEX_FL,
EXT2_SECRM_FL,
EXT2_UNRM_FL,
EXT2_SYNC_FL,
@@ -144,26 +24,31 @@ const uint32_t e2attr_flags_value[] ALIGN4 = {
EXT2_NODUMP_FL,
EXT2_NOATIME_FL,
EXT2_COMPR_FL,
+ EXT2_ECOMPR_FL,
EXT3_JOURNAL_DATA_FL,
+ EXT2_INDEX_FL,
EXT2_NOTAIL_FL,
- EXT2_TOPDIR_FL
+ EXT2_TOPDIR_FL,
+ EXT2_EXTENT_FL,
+ EXT2_NOCOW_FL,
+ EXT2_CASEFOLD_FL,
+ EXT2_INLINE_DATA_FL,
+ EXT2_PROJINHERIT_FL,
+ EXT2_VERITY_FL,
};
const char e2attr_flags_sname[] ALIGN1 =
#ifdef ENABLE_COMPRESSION
- "BZXE"
+ "BZX"
#endif
- "I"
- "suSDiadAcjtT";
+ "suSDiadAcEjItTeCFNPV";
static const char e2attr_flags_lname[] ALIGN1 =
#ifdef ENABLE_COMPRESSION
"Compressed_File" "\0"
"Compressed_Dirty_File" "\0"
"Compression_Raw_Access" "\0"
- "Compression_Error" "\0"
#endif
- "Indexed_directory" "\0"
"Secure_Deletion" "\0"
"Undelete" "\0"
"Synchronous_Updates" "\0"
@@ -173,41 +58,54 @@ static const char e2attr_flags_lname[] ALIGN1 =
"No_Dump" "\0"
"No_Atime" "\0"
"Compression_Requested" "\0"
+ "Encrypted" "\0"
"Journaled_Data" "\0"
+ "Indexed_directory" "\0"
"No_Tailmerging" "\0"
"Top_of_Directory_Hierarchies" "\0"
+ "Extents" "\0"
+ "No_COW" "\0"
+ "Casefold" "\0"
+ "Inline_Data" "\0"
+ "Project_Hierarchy" "\0"
+ "Verity" "\0"
/* Another trailing NUL is added by compiler */;
-void print_e2flags(FILE *f, unsigned long flags, unsigned options)
+void print_e2flags_long(unsigned flags)
+{
+ const uint32_t *fv;
+ const char *fn;
+ int first = 1;
+
+ fv = e2attr_flags_value;
+ fn = e2attr_flags_lname;
+ do {
+ if (flags & *fv) {
+ if (!first)
+ fputs(", ", stdout);
+ fputs(fn, stdout);
+ first = 0;
+ }
+ fv++;
+ fn += strlen(fn) + 1;
+ } while (*fn);
+ if (first)
+ fputs("---", stdout);
+}
+
+void print_e2flags(unsigned flags)
{
const uint32_t *fv;
const char *fn;
fv = e2attr_flags_value;
- if (options & PFOPT_LONG) {
- int first = 1;
- fn = e2attr_flags_lname;
- do {
- if (flags & *fv) {
- if (!first)
- fputs(", ", f);
- fputs(fn, f);
- first = 0;
- }
- fv++;
- fn += strlen(fn) + 1;
- } while (*fn);
- if (first)
- fputs("---", f);
- } else {
- fn = e2attr_flags_sname;
- do {
- char c = '-';
- if (flags & *fv)
- c = *fn;
- fputc(c, f);
- fv++;
- fn++;
- } while (*fn);
- }
+ fn = e2attr_flags_sname;
+ do {
+ char c = '-';
+ if (flags & *fv)
+ c = *fn;
+ putchar(c);
+ fv++;
+ fn++;
+ } while (*fn);
}
diff --git a/e2fsprogs/e2fs_lib.h b/e2fsprogs/e2fs_lib.h
index ae28c353b..bab447a94 100644
--- a/e2fsprogs/e2fs_lib.h
+++ b/e2fsprogs/e2fs_lib.h
@@ -11,25 +11,9 @@
PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
-/* Iterate a function on each entry of a directory */
-int iterate_on_dir(const char *dir_name,
- int FAST_FUNC (*func)(const char *, struct dirent *, void *),
- void *private);
-
-/* Get/set a file version on an ext2 file system */
-int fgetsetversion(const char *name, unsigned long *get_version, unsigned long set_version);
-#define fgetversion(name, version) fgetsetversion(name, version, 0)
-#define fsetversion(name, version) fgetsetversion(name, NULL, version)
-
-/* Get/set a file flags on an ext2 file system */
-int fgetsetflags(const char *name, unsigned long *get_flags, unsigned long set_flags);
-#define fgetflags(name, flags) fgetsetflags(name, flags, 0)
-#define fsetflags(name, flags) fgetsetflags(name, NULL, flags)
-
-/* Must be 1 for compatibility with 'int long_format'. */
-#define PFOPT_LONG 1
/* Print file attributes on an ext2 file system */
-void print_e2flags(FILE *f, unsigned long flags, unsigned options);
+void print_e2flags_long(unsigned flags);
+void print_e2flags(unsigned flags);
extern const uint32_t e2attr_flags_value[];
extern const char e2attr_flags_sname[];
diff --git a/e2fsprogs/lsattr.c b/e2fsprogs/lsattr.c
index 91205ff65..c9f353ce5 100644
--- a/e2fsprogs/lsattr.c
+++ b/e2fsprogs/lsattr.c
@@ -21,52 +21,79 @@
//kbuild:lib-$(CONFIG_LSATTR) += lsattr.o e2fs_lib.o
//usage:#define lsattr_trivial_usage
-//usage: "[-Radlv] [FILE]..."
+//usage: "[-Radlpv] [FILE]..."
//usage:#define lsattr_full_usage "\n\n"
//usage: "List ext2 file attributes\n"
//usage: "\n -R Recurse"
-//usage: "\n -a Don't hide entries starting with ."
-//usage: "\n -d List directory entries instead of contents"
+//usage: "\n -a Include names starting with ."
+//usage: "\n -d List directory names, not contents"
+// -a,-d text should match ls --help
//usage: "\n -l List long flag names"
+//usage: "\n -p List project ID"
//usage: "\n -v List version/generation number"
#include "libbb.h"
#include "e2fs_lib.h"
enum {
- OPT_RECUR = 0x1,
- OPT_ALL = 0x2,
- OPT_DIRS_OPT = 0x4,
- OPT_PF_LONG = 0x8,
- OPT_GENERATION = 0x10,
+ OPT_RECUR = 1 << 0,
+ OPT_ALL = 1 << 1,
+ OPT_DIRS_OPT = 1 << 2,
+ OPT_PF_LONG = 1 << 3,
+ OPT_GENERATION = 1 << 4,
+ OPT_PROJID = 1 << 5,
};
static void list_attributes(const char *name)
{
- unsigned long fsflags;
- unsigned long generation;
-
- if (fgetflags(name, &fsflags) != 0)
- goto read_err;
+ unsigned fsflags;
+ int fd, r;
+
+ /* There is no way to run needed ioctls on a symlink.
+ * open(O_PATH | O_NOFOLLOW) _can_ be used to get a fd referring to the symlink,
+ * but ioctls fail on such a fd (tried on 4.12.0 kernel).
+ * e2fsprogs-1.46.2 uses open(O_NOFOLLOW), it fails on symlinks.
+ */
+ fd = open_or_warn(name, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_NOFOLLOW);
+ if (fd < 0)
+ return;
+
+ if (option_mask32 & OPT_PROJID) {
+ struct ext2_fsxattr fsxattr;
+ r = ioctl(fd, EXT2_IOC_FSGETXATTR, &fsxattr);
+ /* note: ^^^ may fail in 32-bit userspace on 64-bit kernel (seen on 4.12.0) */
+ if (r != 0)
+ goto read_err;
+ printf("%5u ", (unsigned)fsxattr.fsx_projid);
+ }
if (option_mask32 & OPT_GENERATION) {
- if (fgetversion(name, &generation) != 0)
+ unsigned generation;
+ r = ioctl(fd, EXT2_IOC_GETVERSION, &generation);
+ if (r != 0)
goto read_err;
- printf("%5lu ", generation);
+ printf("%-10u ", generation);
}
+ r = ioctl(fd, EXT2_IOC_GETFLAGS, &fsflags);
+ if (r != 0)
+ goto read_err;
+
+ close(fd);
+
if (option_mask32 & OPT_PF_LONG) {
printf("%-28s ", name);
- print_e2flags(stdout, fsflags, PFOPT_LONG);
+ print_e2flags_long(fsflags);
bb_putchar('\n');
} else {
- print_e2flags(stdout, fsflags, 0);
+ print_e2flags(fsflags);
printf(" %s\n", name);
}
return;
read_err:
bb_perror_msg("reading %s", name);
+ close(fd);
}
static int FAST_FUNC lsattr_dir_proc(const char *dir_name,
@@ -79,9 +106,13 @@ static int FAST_FUNC lsattr_dir_proc(const char *dir_name,
path = concat_path_file(dir_name, de->d_name);
if (lstat(path, &st) != 0)
- bb_perror_msg("stat %s", path);
+ bb_perror_msg("can't stat '%s'", path);
+
else if (de->d_name[0] != '.' || (option_mask32 & OPT_ALL)) {
- list_attributes(path);
+ /* Don't try to open device files, fifos etc */
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode) || S_ISDIR(st.st_mode))
+ list_attributes(path);
+
if (S_ISDIR(st.st_mode) && (option_mask32 & OPT_RECUR)
&& !DOT_OR_DOTDOT(de->d_name)
) {
@@ -100,7 +131,7 @@ static void lsattr_args(const char *name)
struct stat st;
if (lstat(name, &st) == -1) {
- bb_perror_msg("stat %s", name);
+ bb_perror_msg("can't stat '%s'", name);
} else if (S_ISDIR(st.st_mode) && !(option_mask32 & OPT_DIRS_OPT)) {
iterate_on_dir(name, lsattr_dir_proc, NULL);
} else {
@@ -111,7 +142,7 @@ static void lsattr_args(const char *name)
int lsattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int lsattr_main(int argc UNUSED_PARAM, char **argv)
{
- getopt32(argv, "Radlv");
+ getopt32(argv, "Radlvp");
argv += optind;
if (!*argv)
diff --git a/editors/awk.c b/editors/awk.c
index b4f6a3741..3adbca7aa 100644
--- a/editors/awk.c
+++ b/editors/awk.c
@@ -66,6 +66,8 @@
#endif
#ifndef debug_printf_parse
# define debug_printf_parse(...) (fprintf(stderr, __VA_ARGS__))
+#else
+# define debug_parse_print_tc(...) ((void)0)
#endif
@@ -91,7 +93,6 @@ enum {
};
#define MAXVARFMT 240
-#define MINNVBLOCK 64
/* variable flags */
#define VF_NUMBER 0x0001 /* 1 = primary type is number */
@@ -101,7 +102,7 @@ enum {
#define VF_USER 0x0200 /* 1 = user input (may be numeric string) */
#define VF_SPECIAL 0x0400 /* 1 = requires extra handling when changed */
#define VF_WALK 0x0800 /* 1 = variable has alloc'd x.walker list */
-#define VF_FSTR 0x1000 /* 1 = var::string points to fstring buffer */
+#define VF_FSTR 0x1000 /* 1 = don't free() var::string (not malloced, or is owned by something else) */
#define VF_CHILD 0x2000 /* 1 = function arg; x.parent points to source */
#define VF_DIRTY 0x4000 /* 1 = variable was set explicitly */
@@ -118,8 +119,8 @@ typedef struct walker_list {
/* Variable */
typedef struct var_s {
unsigned type; /* flags */
- double number;
char *string;
+ double number;
union {
int aidx; /* func arg idx (for compilation stage) */
struct xhash_s *array; /* array ptr */
@@ -138,6 +139,7 @@ typedef struct chain_s {
/* Function */
typedef struct func_s {
unsigned nargs;
+ smallint defined;
struct chain_s body;
} func;
@@ -177,7 +179,7 @@ typedef struct node_s {
struct node_s *n;
var *v;
int aidx;
- char *new_progname;
+ const char *new_progname;
regex_t *re;
} l;
union {
@@ -190,91 +192,120 @@ typedef struct node_s {
} a;
} node;
-/* Block of temporary variables */
-typedef struct nvblock_s {
- int size;
- var *pos;
- struct nvblock_s *prev;
- struct nvblock_s *next;
- var nv[];
-} nvblock;
-
typedef struct tsplitter_s {
node n;
regex_t re[2];
} tsplitter;
/* simple token classes */
-/* Order and hex values are very important!!! See next_token() */
-#define TC_SEQSTART (1 << 0) /* ( */
-#define TC_SEQTERM (1 << 1) /* ) */
-#define TC_REGEXP (1 << 2) /* /.../ */
-#define TC_OUTRDR (1 << 3) /* | > >> */
-#define TC_UOPPOST (1 << 4) /* unary postfix operator */
-#define TC_UOPPRE1 (1 << 5) /* unary prefix operator */
-#define TC_BINOPX (1 << 6) /* two-opnd operator */
-#define TC_IN (1 << 7)
-#define TC_COMMA (1 << 8)
-#define TC_PIPE (1 << 9) /* input redirection pipe */
-#define TC_UOPPRE2 (1 << 10) /* unary prefix operator */
-#define TC_ARRTERM (1 << 11) /* ] */
-#define TC_GRPSTART (1 << 12) /* { */
-#define TC_GRPTERM (1 << 13) /* } */
-#define TC_SEMICOL (1 << 14)
-#define TC_NEWLINE (1 << 15)
-#define TC_STATX (1 << 16) /* ctl statement (for, next...) */
-#define TC_WHILE (1 << 17)
-#define TC_ELSE (1 << 18)
-#define TC_BUILTIN (1 << 19)
+/* order and hex values are very important!!! See next_token() */
+#define TC_LPAREN (1 << 0) /* ( */
+#define TC_RPAREN (1 << 1) /* ) */
+#define TC_REGEXP (1 << 2) /* /.../ */
+#define TC_OUTRDR (1 << 3) /* | > >> */
+#define TC_UOPPOST (1 << 4) /* unary postfix operator ++ -- */
+#define TC_UOPPRE1 (1 << 5) /* unary prefix operator ++ -- $ */
+#define TC_BINOPX (1 << 6) /* two-opnd operator */
+#define TC_IN (1 << 7) /* 'in' */
+#define TC_COMMA (1 << 8) /* , */
+#define TC_PIPE (1 << 9) /* input redirection pipe | */
+#define TC_UOPPRE2 (1 << 10) /* unary prefix operator + - ! */
+#define TC_ARRTERM (1 << 11) /* ] */
+#define TC_LBRACE (1 << 12) /* { */
+#define TC_RBRACE (1 << 13) /* } */
+#define TC_SEMICOL (1 << 14) /* ; */
+#define TC_NEWLINE (1 << 15)
+#define TC_STATX (1 << 16) /* ctl statement (for, next...) */
+#define TC_WHILE (1 << 17) /* 'while' */
+#define TC_ELSE (1 << 18) /* 'else' */
+#define TC_BUILTIN (1 << 19)
/* This costs ~50 bytes of code.
* A separate class to support deprecated "length" form. If we don't need that
* (i.e. if we demand that only "length()" with () is valid), then TC_LENGTH
* can be merged with TC_BUILTIN:
*/
-#define TC_LENGTH (1 << 20)
-#define TC_GETLINE (1 << 21)
-#define TC_FUNCDECL (1 << 22) /* 'function' 'func' */
-#define TC_BEGIN (1 << 23)
-#define TC_END (1 << 24)
-#define TC_EOF (1 << 25)
-#define TC_VARIABLE (1 << 26)
-#define TC_ARRAY (1 << 27)
-#define TC_FUNCTION (1 << 28)
-#define TC_STRING (1 << 29)
-#define TC_NUMBER (1 << 30)
-
-#define TC_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2)
-
-/* combined token classes */
-#define TC_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
-//#define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
-#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION \
- | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
- | TC_SEQSTART | TC_STRING | TC_NUMBER)
-
-#define TC_STATEMNT (TC_STATX | TC_WHILE)
-#define TC_OPTERM (TC_SEMICOL | TC_NEWLINE)
+#define TC_LENGTH (1 << 20) /* 'length' */
+#define TC_GETLINE (1 << 21) /* 'getline' */
+#define TC_FUNCDECL (1 << 22) /* 'function' 'func' */
+#define TC_BEGIN (1 << 23) /* 'BEGIN' */
+#define TC_END (1 << 24) /* 'END' */
+#define TC_EOF (1 << 25)
+#define TC_VARIABLE (1 << 26) /* name */
+#define TC_ARRAY (1 << 27) /* name[ */
+#define TC_FUNCTION (1 << 28) /* name( */
+#define TC_STRING (1 << 29) /* "..." */
+#define TC_NUMBER (1 << 30)
+
+#ifndef debug_parse_print_tc
+static void debug_parse_print_tc(uint32_t n)
+{
+ if (n & TC_LPAREN ) debug_printf_parse(" LPAREN" );
+ if (n & TC_RPAREN ) debug_printf_parse(" RPAREN" );
+ if (n & TC_REGEXP ) debug_printf_parse(" REGEXP" );
+ if (n & TC_OUTRDR ) debug_printf_parse(" OUTRDR" );
+ if (n & TC_UOPPOST ) debug_printf_parse(" UOPPOST" );
+ if (n & TC_UOPPRE1 ) debug_printf_parse(" UOPPRE1" );
+ if (n & TC_BINOPX ) debug_printf_parse(" BINOPX" );
+ if (n & TC_IN ) debug_printf_parse(" IN" );
+ if (n & TC_COMMA ) debug_printf_parse(" COMMA" );
+ if (n & TC_PIPE ) debug_printf_parse(" PIPE" );
+ if (n & TC_UOPPRE2 ) debug_printf_parse(" UOPPRE2" );
+ if (n & TC_ARRTERM ) debug_printf_parse(" ARRTERM" );
+ if (n & TC_LBRACE ) debug_printf_parse(" LBRACE" );
+ if (n & TC_RBRACE ) debug_printf_parse(" RBRACE" );
+ if (n & TC_SEMICOL ) debug_printf_parse(" SEMICOL" );
+ if (n & TC_NEWLINE ) debug_printf_parse(" NEWLINE" );
+ if (n & TC_STATX ) debug_printf_parse(" STATX" );
+ if (n & TC_WHILE ) debug_printf_parse(" WHILE" );
+ if (n & TC_ELSE ) debug_printf_parse(" ELSE" );
+ if (n & TC_BUILTIN ) debug_printf_parse(" BUILTIN" );
+ if (n & TC_LENGTH ) debug_printf_parse(" LENGTH" );
+ if (n & TC_GETLINE ) debug_printf_parse(" GETLINE" );
+ if (n & TC_FUNCDECL) debug_printf_parse(" FUNCDECL");
+ if (n & TC_BEGIN ) debug_printf_parse(" BEGIN" );
+ if (n & TC_END ) debug_printf_parse(" END" );
+ if (n & TC_EOF ) debug_printf_parse(" EOF" );
+ if (n & TC_VARIABLE) debug_printf_parse(" VARIABLE");
+ if (n & TC_ARRAY ) debug_printf_parse(" ARRAY" );
+ if (n & TC_FUNCTION) debug_printf_parse(" FUNCTION");
+ if (n & TC_STRING ) debug_printf_parse(" STRING" );
+ if (n & TC_NUMBER ) debug_printf_parse(" NUMBER" );
+}
+#endif
+
+/* combined token classes ("token [class] sets") */
+#define TS_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2)
+
+#define TS_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
+//#define TS_UNARYOP (TS_UOPPRE | TC_UOPPOST)
+#define TS_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION \
+ | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
+ | TC_LPAREN | TC_STRING | TC_NUMBER)
+
+#define TS_LVALUE (TC_VARIABLE | TC_ARRAY)
+#define TS_STATEMNT (TC_STATX | TC_WHILE)
/* word tokens, cannot mean something else if not expected */
-#define TC_WORD (TC_IN | TC_STATEMNT | TC_ELSE \
- | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
- | TC_FUNCDECL | TC_BEGIN | TC_END)
+#define TS_WORD (TC_IN | TS_STATEMNT | TC_ELSE \
+ | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
+ | TC_FUNCDECL | TC_BEGIN | TC_END)
/* discard newlines after these */
-#define TC_NOTERM (TC_COMMA | TC_GRPSTART | TC_GRPTERM \
- | TC_BINOP | TC_OPTERM)
+#define TS_NOTERM (TS_BINOP | TC_COMMA | TC_LBRACE | TC_RBRACE \
+ | TC_SEMICOL | TC_NEWLINE)
/* what can expression begin with */
-#define TC_OPSEQ (TC_OPERAND | TC_UOPPRE | TC_REGEXP)
+#define TS_OPSEQ (TS_OPERAND | TS_UOPPRE | TC_REGEXP)
/* what can group begin with */
-#define TC_GRPSEQ (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART)
+#define TS_GRPSEQ (TS_OPSEQ | TS_STATEMNT \
+ | TC_SEMICOL | TC_NEWLINE | TC_LBRACE)
-/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */
+/* if previous token class is CONCAT_L and next is CONCAT_R, concatenation */
/* operator is inserted between them */
-#define TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM \
+#define TS_CONCAT_L (TC_VARIABLE | TC_ARRTERM | TC_RPAREN \
| TC_STRING | TC_NUMBER | TC_UOPPOST \
| TC_LENGTH)
-#define TC_CONCAT2 (TC_OPERAND | TC_UOPPRE)
+#define TS_CONCAT_R (TS_OPERAND | TS_UOPPRE)
#define OF_RES1 0x010000
#define OF_RES2 0x020000
@@ -284,13 +315,12 @@ typedef struct tsplitter_s {
#define OF_CHECKED 0x200000
#define OF_REQUIRED 0x400000
-
/* combined operator flags */
#define xx 0
#define xV OF_RES2
#define xS (OF_RES2 | OF_STR2)
#define Vx OF_RES1
-#define Rx (OF_RES1 | OF_NUM1 | OF_REQUIRED)
+#define Rx OF_REQUIRED
#define VV (OF_RES1 | OF_RES2)
#define Nx (OF_RES1 | OF_NUM1)
#define NV (OF_RES1 | OF_NUM1 | OF_RES2)
@@ -302,8 +332,7 @@ typedef struct tsplitter_s {
#define OPNMASK 0x007F
/* operator priority is a highest byte (even: r->l, odd: l->r grouping)
- * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1,
- * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string
+ * (for builtins it has different meaning)
*/
#undef P
#undef PRIMASK
@@ -313,10 +342,8 @@ typedef struct tsplitter_s {
#define PRIMASK2 0x7E000000
/* Operation classes */
-
#define SHIFT_TIL_THIS 0x0600
#define RECUR_FROM_THIS 0x1000
-
enum {
OC_DELETE = 0x0100, OC_EXEC = 0x0200, OC_NEWSOURCE = 0x0300,
OC_PRINT = 0x0400, OC_PRINTF = 0x0500, OC_WALKINIT = 0x0600,
@@ -358,8 +385,8 @@ enum {
#define NTCC '\377'
static const char tokenlist[] ALIGN1 =
- "\1(" NTC /* TC_SEQSTART */
- "\1)" NTC /* TC_SEQTERM */
+ "\1(" NTC /* TC_LPAREN */
+ "\1)" NTC /* TC_RPAREN */
"\1/" NTC /* TC_REGEXP */
"\2>>" "\1>" "\1|" NTC /* TC_OUTRDR */
"\2++" "\2--" NTC /* TC_UOPPOST */
@@ -376,8 +403,8 @@ static const char tokenlist[] ALIGN1 =
"\1|" NTC /* TC_PIPE */
"\1+" "\1-" "\1!" NTC /* TC_UOPPRE2 */
"\1]" NTC /* TC_ARRTERM */
- "\1{" NTC /* TC_GRPSTART */
- "\1}" NTC /* TC_GRPTERM */
+ "\1{" NTC /* TC_LBRACE */
+ "\1}" NTC /* TC_RBRACE */
"\1;" NTC /* TC_SEMICOL */
"\1\n" NTC /* TC_NEWLINE */
"\2if" "\2do" "\3for" "\5break" /* TC_STATX */
@@ -391,7 +418,7 @@ static const char tokenlist[] ALIGN1 =
"\5close" "\6system" "\6fflush" "\5atan2"
"\3cos" "\3exp" "\3int" "\3log"
"\4rand" "\3sin" "\4sqrt" "\5srand"
- "\6gensub" "\4gsub" "\5index" /* "\6length" was here */
+ "\6gensub" "\4gsub" "\5index" /* "\6length" was here */
"\5match" "\5split" "\7sprintf" "\3sub"
"\6substr" "\7systime" "\10strftime" "\6mktime"
"\7tolower" "\7toupper" NTC
@@ -403,25 +430,32 @@ static const char tokenlist[] ALIGN1 =
/* compiler adds trailing "\0" */
;
-#define OC_B OC_BUILTIN
-
static const uint32_t tokeninfo[] ALIGN4 = {
0,
0,
- OC_REGEXP,
+#define TI_REGEXP OC_REGEXP
+ TI_REGEXP,
xS|'a', xS|'w', xS|'|',
OC_UNARY|xV|P(9)|'p', OC_UNARY|xV|P(9)|'m',
- OC_UNARY|xV|P(9)|'P', OC_UNARY|xV|P(9)|'M', OC_FIELD|xV|P(5),
+#define TI_PREINC (OC_UNARY|xV|P(9)|'P')
+#define TI_PREDEC (OC_UNARY|xV|P(9)|'M')
+ TI_PREINC, TI_PREDEC, OC_FIELD|xV|P(5),
OC_COMPARE|VV|P(39)|5, OC_MOVE|VV|P(74), OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
OC_BINARY|NV|P(29)|'+', OC_BINARY|NV|P(29)|'-', OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
OC_BINARY|NV|P(25)|'/', OC_BINARY|NV|P(25)|'%', OC_BINARY|NV|P(15)|'&', OC_BINARY|NV|P(25)|'*',
OC_COMPARE|VV|P(39)|4, OC_COMPARE|VV|P(39)|3, OC_COMPARE|VV|P(39)|0, OC_COMPARE|VV|P(39)|1,
- OC_COMPARE|VV|P(39)|2, OC_MATCH|Sx|P(45)|'!', OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55),
- OC_LOR|Vx|P(59), OC_TERNARY|Vx|P(64)|'?', OC_COLON|xx|P(67)|':',
- OC_IN|SV|P(49), /* TC_IN */
- OC_COMMA|SS|P(80),
- OC_PGETLINE|SV|P(37),
+#define TI_LESS (OC_COMPARE|VV|P(39)|2)
+ TI_LESS, OC_MATCH|Sx|P(45)|'!', OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55),
+#define TI_TERNARY (OC_TERNARY|Vx|P(64)|'?')
+#define TI_COLON (OC_COLON|xx|P(67)|':')
+ OC_LOR|Vx|P(59), TI_TERNARY, TI_COLON,
+#define TI_IN (OC_IN|SV|P(49))
+ TI_IN,
+#define TI_COMMA (OC_COMMA|SS|P(80))
+ TI_COMMA,
+#define TI_PGETLINE (OC_PGETLINE|SV|P(37))
+ TI_PGETLINE,
OC_UNARY|xV|P(19)|'+', OC_UNARY|xV|P(19)|'-', OC_UNARY|xV|P(19)|'!',
0, /* ] */
0,
@@ -434,20 +468,45 @@ static const uint32_t tokeninfo[] ALIGN4 = {
OC_RETURN|Vx, OC_EXIT|Nx,
ST_WHILE,
0, /* else */
- OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83),
- OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83),
- OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83),
- OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
- OC_FBLTIN|F_rn, OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
- OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), /* OC_FBLTIN|Sx|F_le, was here */
- OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF, OC_B|B_su|P(0xb6),
- OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti, OC_B|B_ti|P(0x0b), OC_B|B_mt|P(0x0b),
- OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
- OC_FBLTIN|Sx|F_le, /* TC_LENGTH */
- OC_GETLINE|SV|P(0),
- 0, 0,
- 0,
- 0 /* TC_END */
+// OC_B's are builtins with enforced minimum number of arguments (two upper bits).
+// Highest byte bit pattern: nn s3s2s1 v3v2v1
+// nn - min. number of args, sN - resolve Nth arg to string, vN - resolve to var
+// OC_F's are builtins with zero or one argument.
+// |Rx| enforces that arg is present for: system, close, cos, sin, exp, int, log, sqrt
+// Check for no args is present in builtins' code (not in this table): rand, systime
+// Have one _optional_ arg: fflush, srand, length
+#define OC_B OC_BUILTIN
+#define OC_F OC_FBLTIN
+#define A1 P(0x40) /*one arg*/
+#define A2 P(0x80) /*two args*/
+#define A3 P(0xc0) /*three args*/
+#define __v P(1)
+#define _vv P(3)
+#define __s__v P(9)
+#define __s_vv P(0x0b)
+#define __svvv P(0x0f)
+#define _ss_vv P(0x1b)
+#define _s_vv_ P(0x16)
+#define ss_vv_ P(0x36)
+ OC_B|B_an|_vv|A2, OC_B|B_co|__v|A1, OC_B|B_ls|_vv|A2, OC_B|B_or|_vv|A2, // and compl lshift or
+ OC_B|B_rs|_vv|A2, OC_B|B_xo|_vv|A2, // rshift xor
+ OC_F|F_cl|Sx|Rx, OC_F|F_sy|Sx|Rx, OC_F|F_ff|Sx, OC_B|B_a2|_vv|A2, // close system fflush atan2
+ OC_F|F_co|Nx|Rx, OC_F|F_ex|Nx|Rx, OC_F|F_in|Nx|Rx, OC_F|F_lg|Nx|Rx, // cos exp int log
+ OC_F|F_rn, OC_F|F_si|Nx|Rx, OC_F|F_sq|Nx|Rx, OC_F|F_sr|Nx, // rand sin sqrt srand
+ OC_B|B_ge|_s_vv_|A3,OC_B|B_gs|ss_vv_|A2,OC_B|B_ix|_ss_vv|A2, // gensub gsub index /*length was here*/
+ OC_B|B_ma|__s__v|A2,OC_B|B_sp|__s_vv|A2,OC_SPRINTF, OC_B|B_su|ss_vv_|A2,// match split sprintf sub
+ OC_B|B_ss|__svvv|A2,OC_F|F_ti, OC_B|B_ti|__s_vv, OC_B|B_mt|__s_vv, // substr systime strftime mktime
+ OC_B|B_lo|__s__v|A1,OC_B|B_up|__s__v|A1, // tolower toupper
+ OC_F|F_le|Sx, // length
+ OC_GETLINE|SV, // getline
+ 0, 0, // func function
+ 0, // BEGIN
+ 0 // END
+#undef A1
+#undef A2
+#undef A3
+#undef OC_B
+#undef OC_F
};
/* internal variable names and their initial values */
@@ -488,21 +547,29 @@ struct globals {
chain *seq;
node *break_ptr, *continue_ptr;
rstream *iF;
- xhash *vhash, *ahash, *fdhash, *fnhash;
+ xhash *ahash; /* argument names, used only while parsing function bodies */
+ xhash *fnhash; /* function names, used only in parsing stage */
+ xhash *vhash; /* variables and arrays */
+ //xhash *fdhash; /* file objects, used only in execution stage */
+ //we are reusing ahash as fdhash, via define (see later)
const char *g_progname;
int g_lineno;
int nfields;
int maxfields; /* used in fsrealloc() only */
var *Fields;
- nvblock *g_cb;
char *g_pos;
- char *g_buf;
+ char g_saved_ch;
smallint icase;
smallint exiting;
smallint nextrec;
smallint nextfile;
smallint is_f0_split;
smallint t_rollback;
+
+ /* former statics from various functions */
+ smallint next_token__concat_inserted;
+ uint32_t next_token__save_tclass;
+ uint32_t next_token__save_info;
};
struct globals2 {
uint32_t t_info; /* often used */
@@ -515,32 +582,35 @@ struct globals2 {
/* former statics from various functions */
char *split_f0__fstrings;
- uint32_t next_token__save_tclass;
- uint32_t next_token__save_info;
- uint32_t next_token__ltclass;
- smallint next_token__concat_inserted;
-
- smallint next_input_file__files_happen;
rstream next_input_file__rsm;
+ smallint next_input_file__files_happen;
+
+ smalluint exitcode;
- var *evaluate__fnargs;
unsigned evaluate__seed;
+ var *evaluate__fnargs;
regex_t evaluate__sreg;
- var ptest__v;
+ var ptest__tmpvar;
+ var awk_printf__tmpvar;
+ var as_regex__tmpvar;
+ var exit__tmpvar;
+ var main__tmpvar;
tsplitter exec_builtin__tspl;
/* biggest and least used members go last */
tsplitter fsplitter, rsplitter;
+
+ char g_buf[MAXVARFMT + 1];
};
#define G1 (ptr_to_globals[-1])
#define G (*(struct globals2 *)ptr_to_globals)
/* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */
-/*char G1size[sizeof(G1)]; - 0x74 */
-/*char Gsize[sizeof(G)]; - 0x1c4 */
+//char G1size[sizeof(G1)]; // 0x70
+//char Gsize[sizeof(G)]; // 0x2f8
/* Trying to keep most of members accessible with short offsets: */
-/*char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+//char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; // 0x7c
#define t_double (G1.t_double )
#define beginseq (G1.beginseq )
#define mainseq (G1.mainseq )
@@ -549,18 +619,20 @@ struct globals2 {
#define break_ptr (G1.break_ptr )
#define continue_ptr (G1.continue_ptr)
#define iF (G1.iF )
-#define vhash (G1.vhash )
#define ahash (G1.ahash )
-#define fdhash (G1.fdhash )
#define fnhash (G1.fnhash )
+#define vhash (G1.vhash )
+#define fdhash ahash
+//^^^^^^^^^^^^^^^^^^ ahash is cleared after every function parsing,
+// and ends up empty after parsing phase. Thus, we can simply reuse it
+// for fdhash in execution stage.
#define g_progname (G1.g_progname )
#define g_lineno (G1.g_lineno )
#define nfields (G1.nfields )
#define maxfields (G1.maxfields )
#define Fields (G1.Fields )
-#define g_cb (G1.g_cb )
#define g_pos (G1.g_pos )
-#define g_buf (G1.g_buf )
+#define g_saved_ch (G1.g_saved_ch )
#define icase (G1.icase )
#define exiting (G1.exiting )
#define nextrec (G1.nextrec )
@@ -574,25 +646,13 @@ struct globals2 {
#define intvar (G.intvar )
#define fsplitter (G.fsplitter )
#define rsplitter (G.rsplitter )
+#define g_buf (G.g_buf )
#define INIT_G() do { \
SET_PTR_TO_GLOBALS((char*)xzalloc(sizeof(G1)+sizeof(G)) + sizeof(G1)); \
- G.next_token__ltclass = TC_OPTERM; \
+ t_tclass = TC_NEWLINE; \
G.evaluate__seed = 1; \
} while (0)
-
-/* function prototypes */
-static void handle_special(var *);
-static node *parse_expr(uint32_t);
-static void chain_group(void);
-static var *evaluate(node *, var *);
-static rstream *next_input_file(void);
-static int fmt_num(char *, int, const char *, double, int);
-static int awk_exit(int) NORETURN;
-
-/* ---- error handling ---- */
-
-static const char EMSG_INTERNAL_ERROR[] ALIGN1 = "Internal error";
static const char EMSG_UNEXP_EOS[] ALIGN1 = "Unexpected end of string";
static const char EMSG_UNEXP_TOKEN[] ALIGN1 = "Unexpected token";
static const char EMSG_DIV_BY_ZERO[] ALIGN1 = "Division by zero";
@@ -604,10 +664,7 @@ static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field";
-static void zero_out_var(var *vp)
-{
- memset(vp, 0, sizeof(*vp));
-}
+static int awk_exit(void) NORETURN;
static void syntax_error(const char *message) NORETURN;
static void syntax_error(const char *message)
@@ -638,12 +695,40 @@ static xhash *hash_init(void)
return newhash;
}
+static void hash_clear(xhash *hash)
+{
+ unsigned i;
+ hash_item *hi, *thi;
+
+ for (i = 0; i < hash->csize; i++) {
+ hi = hash->items[i];
+ while (hi) {
+ thi = hi;
+ hi = hi->next;
+//FIXME: this assumes that it's a hash of *variables*:
+ free(thi->data.v.string);
+ free(thi);
+ }
+ hash->items[i] = NULL;
+ }
+ hash->glen = hash->nel = 0;
+}
+
+#if 0 //UNUSED
+static void hash_free(xhash *hash)
+{
+ hash_clear(hash);
+ free(hash->items);
+ free(hash);
+}
+#endif
+
/* find item in hash, return ptr to data, NULL if not found */
-static void *hash_search(xhash *hash, const char *name)
+static NOINLINE void *hash_search3(xhash *hash, const char *name, unsigned idx)
{
hash_item *hi;
- hi = hash->items[hashidx(name) % hash->csize];
+ hi = hash->items[idx % hash->csize];
while (hi) {
if (strcmp(hi->name, name) == 0)
return &hi->data;
@@ -652,6 +737,11 @@ static void *hash_search(xhash *hash, const char *name)
return NULL;
}
+static void *hash_search(xhash *hash, const char *name)
+{
+ return hash_search3(hash, name, hashidx(name));
+}
+
/* grow hash if it becomes too big */
static void hash_rebuild(xhash *hash)
{
@@ -687,16 +777,17 @@ static void *hash_find(xhash *hash, const char *name)
unsigned idx;
int l;
- hi = hash_search(hash, name);
+ idx = hashidx(name);
+ hi = hash_search3(hash, name, idx);
if (!hi) {
- if (++hash->nel / hash->csize > 10)
+ if (++hash->nel > hash->csize * 8)
hash_rebuild(hash);
l = strlen(name) + 1;
hi = xzalloc(sizeof(*hi) + l);
strcpy(hi->name, name);
- idx = hashidx(name) % hash->csize;
+ idx = idx % hash->csize;
hi->next = hash->items[idx];
hash->items[idx] = hi;
hash->glen += l;
@@ -731,7 +822,7 @@ static void hash_remove(xhash *hash, const char *name)
static char *skip_spaces(char *p)
{
- while (1) {
+ for (;;) {
if (*p == '\\' && p[1] == '\n') {
p++;
t_lineno++;
@@ -747,8 +838,10 @@ static char *skip_spaces(char *p)
static char *nextword(char **s)
{
char *p = *s;
- while (*(*s)++ != '\0')
+ char *q = p;
+ while (*q++ != '\0')
continue;
+ *s = q;
return p;
}
@@ -811,10 +904,27 @@ static double my_strtod(char **pp)
/* -------- working with variables (set/get/copy/etc) -------- */
-static xhash *iamarray(var *v)
+static void fmt_num(const char *format, double n)
{
- var *a = v;
+ if (n == (long long)n) {
+ snprintf(g_buf, MAXVARFMT, "%lld", (long long)n);
+ } else {
+ const char *s = format;
+ char c;
+ do { c = *s; } while (c && *++s);
+ if (strchr("diouxX", c)) {
+ snprintf(g_buf, MAXVARFMT, format, (int)n);
+ } else if (strchr("eEfFgGaA", c)) {
+ snprintf(g_buf, MAXVARFMT, format, n);
+ } else {
+ syntax_error(EMSG_INV_FMT);
+ }
+ }
+}
+
+static xhash *iamarray(var *a)
+{
while (a->type & VF_CHILD)
a = a->x.parent;
@@ -825,23 +935,7 @@ static xhash *iamarray(var *v)
return a->x.array;
}
-static void clear_array(xhash *array)
-{
- unsigned i;
- hash_item *hi, *thi;
-
- for (i = 0; i < array->csize; i++) {
- hi = array->items[i];
- while (hi) {
- thi = hi;
- hi = hi->next;
- free(thi->data.v.string);
- free(thi);
- }
- array->items[i] = NULL;
- }
- array->glen = array->nel = 0;
-}
+#define clear_array(array) hash_clear(array)
/* clear a variable */
static var *clrvar(var *v)
@@ -855,6 +949,8 @@ static var *clrvar(var *v)
return v;
}
+static void handle_special(var *);
+
/* assign string value to variable */
static var *setvar_p(var *v, char *value)
{
@@ -901,7 +997,7 @@ static const char *getvar_s(var *v)
{
/* if v is numeric and has no cached string, convert it to string */
if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) {
- fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[CONVFMT]), v->number, TRUE);
+ fmt_num(getvar_s(intvar[CONVFMT]), v->number);
v->string = xstrdup(g_buf);
v->type |= VF_CACHED;
}
@@ -920,6 +1016,7 @@ static double getvar_i(var *v)
v->number = my_strtod(&s);
debug_printf_eval("%f (s:'%s')\n", v->number, s);
if (v->type & VF_USER) {
+//TODO: skip_spaces() also skips backslash+newline, is it intended here?
s = skip_spaces(s);
if (*s != '\0')
v->type &= ~VF_USER;
@@ -981,94 +1078,28 @@ static int istrue(var *v)
return (v->string && v->string[0]);
}
-/* temporary variables allocator. Last allocated should be first freed */
-static var *nvalloc(int n)
-{
- nvblock *pb = NULL;
- var *v, *r;
- int size;
-
- while (g_cb) {
- pb = g_cb;
- if ((g_cb->pos - g_cb->nv) + n <= g_cb->size)
- break;
- g_cb = g_cb->next;
- }
-
- if (!g_cb) {
- size = (n <= MINNVBLOCK) ? MINNVBLOCK : n;
- g_cb = xzalloc(sizeof(nvblock) + size * sizeof(var));
- g_cb->size = size;
- g_cb->pos = g_cb->nv;
- g_cb->prev = pb;
- /*g_cb->next = NULL; - xzalloc did it */
- if (pb)
- pb->next = g_cb;
- }
-
- v = r = g_cb->pos;
- g_cb->pos += n;
-
- while (v < g_cb->pos) {
- v->type = 0;
- v->string = NULL;
- v++;
- }
-
- return r;
-}
-
-static void nvfree(var *v)
-{
- var *p;
-
- if (v < g_cb->nv || v >= g_cb->pos)
- syntax_error(EMSG_INTERNAL_ERROR);
-
- for (p = v; p < g_cb->pos; p++) {
- if ((p->type & (VF_ARRAY | VF_CHILD)) == VF_ARRAY) {
- clear_array(iamarray(p));
- free(p->x.array->items);
- free(p->x.array);
- }
- if (p->type & VF_WALK) {
- walker_list *n;
- walker_list *w = p->x.walker;
- debug_printf_walker("nvfree: freeing walker @%p\n", &p->x.walker);
- p->x.walker = NULL;
- while (w) {
- n = w->prev;
- debug_printf_walker(" free(%p)\n", w);
- free(w);
- w = n;
- }
- }
- clrvar(p);
- }
-
- g_cb->pos = v;
- while (g_cb->prev && g_cb->pos == g_cb->nv) {
- g_cb = g_cb->prev;
- }
-}
-
/* ------- awk program text parsing ------- */
-/* Parse next token pointed by global pos, place results into global ttt.
- * If token isn't expected, give away. Return token class
+/* Parse next token pointed by global pos, place results into global t_XYZ variables.
+ * If token isn't expected, print error message and die.
+ * Return token class (also store it in t_tclass).
*/
static uint32_t next_token(uint32_t expected)
{
-#define concat_inserted (G.next_token__concat_inserted)
-#define save_tclass (G.next_token__save_tclass)
-#define save_info (G.next_token__save_info)
-/* Initialized to TC_OPTERM: */
-#define ltclass (G.next_token__ltclass)
+#define concat_inserted (G1.next_token__concat_inserted)
+#define save_tclass (G1.next_token__save_tclass)
+#define save_info (G1.next_token__save_info)
- char *p, *s;
+ char *p;
const char *tl;
- uint32_t tc;
const uint32_t *ti;
+ uint32_t tc, last_token_class;
+
+ last_token_class = t_tclass; /* t_tclass is initialized to TC_NEWLINE */
+
+ debug_printf_parse("%s() expected(%x):", __func__, expected);
+ debug_parse_print_tc(expected);
+ debug_printf_parse("\n");
if (t_rollback) {
debug_printf_parse("%s: using rolled-back token\n", __func__);
@@ -1080,6 +1111,10 @@ static uint32_t next_token(uint32_t expected)
t_info = save_info;
} else {
p = g_pos;
+ if (g_saved_ch != '\0') {
+ *p = g_saved_ch;
+ g_saved_ch = '\0';
+ }
readnext:
p = skip_spaces(p);
g_lineno = t_lineno;
@@ -1087,15 +1122,12 @@ static uint32_t next_token(uint32_t expected)
while (*p != '\n' && *p != '\0')
p++;
- if (*p == '\n')
- t_lineno++;
-
if (*p == '\0') {
tc = TC_EOF;
debug_printf_parse("%s: token found: TC_EOF\n", __func__);
} else if (*p == '\"') {
/* it's a string */
- t_string = s = ++p;
+ char *s = t_string = ++p;
while (*p != '\"') {
char *pp;
if (*p == '\0' || *p == '\n')
@@ -1110,7 +1142,7 @@ static uint32_t next_token(uint32_t expected)
debug_printf_parse("%s: token found:'%s' TC_STRING\n", __func__, t_string);
} else if ((expected & TC_REGEXP) && *p == '/') {
/* it's regexp */
- t_string = s = ++p;
+ char *s = t_string = ++p;
while (*p != '/') {
if (*p == '\0' || *p == '\n')
syntax_error(EMSG_UNEXP_EOS);
@@ -1141,6 +1173,11 @@ static uint32_t next_token(uint32_t expected)
tc = TC_NUMBER;
debug_printf_parse("%s: token found:%f TC_NUMBER\n", __func__, t_double);
} else {
+ char *end_of_name;
+
+ if (*p == '\n')
+ t_lineno++;
+
/* search for something known */
tl = tokenlist;
tc = 0x00000001;
@@ -1155,9 +1192,9 @@ static uint32_t next_token(uint32_t expected)
* token matches,
* and it's not a longer word,
*/
- if ((tc & (expected | TC_WORD | TC_NEWLINE))
+ if ((tc & (expected | TS_WORD | TC_NEWLINE))
&& strncmp(p, tl, l) == 0
- && !((tc & TC_WORD) && isalnum_(p[l]))
+ && !((tc & TS_WORD) && isalnum_(p[l]))
) {
/* then this is what we are looking for */
t_info = *ti;
@@ -1174,67 +1211,94 @@ static uint32_t next_token(uint32_t expected)
if (!isalnum_(*p))
syntax_error(EMSG_UNEXP_TOKEN); /* no */
/* yes */
- t_string = --p;
- while (isalnum_(*++p)) {
- p[-1] = *p;
- }
- p[-1] = '\0';
- tc = TC_VARIABLE;
- /* also consume whitespace between functionname and bracket */
- if (!(expected & TC_VARIABLE) || (expected & TC_ARRAY))
+ t_string = p;
+ while (isalnum_(*p))
+ p++;
+ end_of_name = p;
+
+ if (last_token_class == TC_FUNCDECL)
+ /* eat space in "function FUNC (...) {...}" declaration */
p = skip_spaces(p);
+ else if (expected & TC_ARRAY) {
+ /* eat space between array name and [ */
+ char *s = skip_spaces(p);
+ if (*s == '[') /* array ref, not just a name? */
+ p = s;
+ }
+ /* else: do NOT consume whitespace after variable name!
+ * gawk allows definition "function FUNC (p) {...}" - note space,
+ * but disallows the call "FUNC (p)" because it isn't one -
+ * expression "v (a)" should NOT be parsed as TC_FUNCTION:
+ * it is a valid concatenation if "v" is a variable,
+ * not a function name (and type of name is not known at parse time).
+ */
+
if (*p == '(') {
+ p++;
tc = TC_FUNCTION;
debug_printf_parse("%s: token found:'%s' TC_FUNCTION\n", __func__, t_string);
+ } else if (*p == '[') {
+ p++;
+ tc = TC_ARRAY;
+ debug_printf_parse("%s: token found:'%s' TC_ARRAY\n", __func__, t_string);
} else {
- if (*p == '[') {
- p++;
- tc = TC_ARRAY;
- debug_printf_parse("%s: token found:'%s' TC_ARRAY\n", __func__, t_string);
- } else
- debug_printf_parse("%s: token found:'%s' TC_VARIABLE\n", __func__, t_string);
+ tc = TC_VARIABLE;
+ debug_printf_parse("%s: token found:'%s' TC_VARIABLE\n", __func__, t_string);
+ if (end_of_name == p) {
+ /* there is no space for trailing NUL in t_string!
+ * We need to save the char we are going to NUL.
+ * (we'll use it in future call to next_token())
+ */
+ g_saved_ch = *end_of_name;
+// especially pathological example is V="abc"; V.2 - it's V concatenated to .2
+// (it evaluates to "abc0.2"). Because of this case, we can't simply cache
+// '.' and analyze it later: we also have to *store it back* in next
+// next_token(), in order to give my_strtod() the undamaged ".2" string.
+ }
}
+ *end_of_name = '\0'; /* terminate t_string */
}
token_found:
g_pos = p;
/* skipping newlines in some cases */
- if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE))
+ if ((last_token_class & TS_NOTERM) && (tc & TC_NEWLINE))
goto readnext;
/* insert concatenation operator when needed */
- debug_printf_parse("%s: %x %x %x concat_inserted?\n", __func__,
- (ltclass & TC_CONCAT1), (tc & TC_CONCAT2), (expected & TC_BINOP));
- if ((ltclass & TC_CONCAT1) && (tc & TC_CONCAT2) && (expected & TC_BINOP)
- && !(ltclass == TC_LENGTH && tc == TC_SEQSTART) /* but not for "length(..." */
+ debug_printf_parse("%s: concat_inserted if all nonzero: %x %x %x %x\n", __func__,
+ (last_token_class & TS_CONCAT_L), (tc & TS_CONCAT_R), (expected & TS_BINOP),
+ !(last_token_class == TC_LENGTH && tc == TC_LPAREN));
+ if ((last_token_class & TS_CONCAT_L) && (tc & TS_CONCAT_R) && (expected & TS_BINOP)
+ && !(last_token_class == TC_LENGTH && tc == TC_LPAREN) /* but not for "length(..." */
) {
concat_inserted = TRUE;
save_tclass = tc;
save_info = t_info;
- tc = TC_BINOP;
+ tc = TC_BINOPX;
t_info = OC_CONCAT | SS | P(35);
}
- debug_printf_parse("%s: t_tclass=tc=%x\n", __func__, t_tclass);
t_tclass = tc;
+ debug_printf_parse("%s: t_tclass=tc=%x\n", __func__, tc);
}
- ltclass = t_tclass;
-
/* Are we ready for this? */
- if (!(ltclass & expected)) {
- syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
+ if (!(t_tclass & expected)) {
+ syntax_error((last_token_class & (TC_NEWLINE | TC_EOF)) ?
EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
}
- debug_printf_parse("%s: returning, ltclass:%x t_double:%f\n", __func__, ltclass, t_double);
- return ltclass;
+ debug_printf_parse("%s: returning, t_double:%f t_tclass:", __func__, t_double);
+ debug_parse_print_tc(t_tclass);
+ debug_printf_parse("\n");
+
+ return t_tclass;
#undef concat_inserted
#undef save_tclass
#undef save_info
-#undef ltclass
}
-static void rollback_token(void)
+static ALWAYS_INLINE void rollback_token(void)
{
t_rollback = TRUE;
}
@@ -1251,169 +1315,188 @@ static node *new_node(uint32_t info)
static void mk_re_node(const char *s, node *n, regex_t *re)
{
- n->info = OC_REGEXP;
+ n->info = TI_REGEXP;
n->l.re = re;
n->r.ire = re + 1;
xregcomp(re, s, REG_EXTENDED);
xregcomp(re + 1, s, REG_EXTENDED | REG_ICASE);
}
-static node *condition(void)
+static node *parse_expr(uint32_t);
+
+static node *parse_lrparen_list(void)
{
- next_token(TC_SEQSTART);
- return parse_expr(TC_SEQTERM);
+ next_token(TC_LPAREN);
+ return parse_expr(TC_RPAREN);
}
/* parse expression terminated by given argument, return ptr
* to built subtree. Terminator is eaten by parse_expr */
-static node *parse_expr(uint32_t iexp)
+static node *parse_expr(uint32_t term_tc)
{
node sn;
node *cn = &sn;
node *vn, *glptr;
- uint32_t tc, xtc;
+ uint32_t tc, expected_tc;
var *v;
- debug_printf_parse("%s(%x)\n", __func__, iexp);
+ debug_printf_parse("%s() term_tc(%x):", __func__, term_tc);
+ debug_parse_print_tc(term_tc);
+ debug_printf_parse("\n");
sn.info = PRIMASK;
sn.r.n = sn.a.n = glptr = NULL;
- xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp;
+ expected_tc = TS_OPERAND | TS_UOPPRE | TC_REGEXP | term_tc;
- while (!((tc = next_token(xtc)) & iexp)) {
+ while (!((tc = next_token(expected_tc)) & term_tc)) {
- if (glptr && (t_info == (OC_COMPARE | VV | P(39) | 2))) {
+ if (glptr && (t_info == TI_LESS)) {
/* input redirection (<) attached to glptr node */
debug_printf_parse("%s: input redir\n", __func__);
cn = glptr->l.n = new_node(OC_CONCAT | SS | P(37));
cn->a.n = glptr;
- xtc = TC_OPERAND | TC_UOPPRE;
+ expected_tc = TS_OPERAND | TS_UOPPRE;
glptr = NULL;
-
- } else if (tc & (TC_BINOP | TC_UOPPOST)) {
- debug_printf_parse("%s: TC_BINOP | TC_UOPPOST tc:%x\n", __func__, tc);
+ continue;
+ }
+ if (tc & (TS_BINOP | TC_UOPPOST)) {
+ debug_printf_parse("%s: TS_BINOP | TC_UOPPOST tc:%x\n", __func__, tc);
/* for binary and postfix-unary operators, jump back over
* previous operators with higher priority */
vn = cn;
while (((t_info & PRIMASK) > (vn->a.n->info & PRIMASK2))
- || ((t_info == vn->info) && ((t_info & OPCLSMASK) == OC_COLON))
+ || ((t_info == vn->info) && t_info == TI_COLON)
) {
vn = vn->a.n;
if (!vn->a.n) syntax_error(EMSG_UNEXP_TOKEN);
}
- if ((t_info & OPCLSMASK) == OC_TERNARY)
+ if (t_info == TI_TERNARY)
+//TODO: why?
t_info += P(6);
cn = vn->a.n->r.n = new_node(t_info);
cn->a.n = vn->a.n;
- if (tc & TC_BINOP) {
+ if (tc & TS_BINOP) {
cn->l.n = vn;
- xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
- if ((t_info & OPCLSMASK) == OC_PGETLINE) {
+//FIXME: this is the place to detect and reject assignments to non-lvalues.
+//Currently we allow "assignments" to consts and temporaries, nonsense like this:
+// awk 'BEGIN { "qwe" = 1 }'
+// awk 'BEGIN { 7 *= 7 }'
+// awk 'BEGIN { length("qwe") = 1 }'
+// awk 'BEGIN { (1+1) += 3 }'
+ expected_tc = TS_OPERAND | TS_UOPPRE | TC_REGEXP;
+ if (t_info == TI_PGETLINE) {
/* it's a pipe */
next_token(TC_GETLINE);
/* give maximum priority to this pipe */
cn->info &= ~PRIMASK;
- xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
+ expected_tc = TS_OPERAND | TS_UOPPRE | TS_BINOP | term_tc;
}
} else {
cn->r.n = vn;
- xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
+ expected_tc = TS_OPERAND | TS_UOPPRE | TS_BINOP | term_tc;
}
vn->a.n = cn;
+ continue;
+ }
- } else {
- debug_printf_parse("%s: other\n", __func__);
- /* for operands and prefix-unary operators, attach them
- * to last node */
- vn = cn;
- cn = vn->r.n = new_node(t_info);
- cn->a.n = vn;
- xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
- if (tc & (TC_OPERAND | TC_REGEXP)) {
- debug_printf_parse("%s: TC_OPERAND | TC_REGEXP\n", __func__);
- xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp;
- /* one should be very careful with switch on tclass -
- * only simple tclasses should be used! */
- switch (tc) {
- case TC_VARIABLE:
- case TC_ARRAY:
- debug_printf_parse("%s: TC_VARIABLE | TC_ARRAY\n", __func__);
- cn->info = OC_VAR;
- v = hash_search(ahash, t_string);
- if (v != NULL) {
- cn->info = OC_FNARG;
- cn->l.aidx = v->x.aidx;
- } else {
- cn->l.v = newvar(t_string);
- }
- if (tc & TC_ARRAY) {
- cn->info |= xS;
- cn->r.n = parse_expr(TC_ARRTERM);
- }
- break;
+ debug_printf_parse("%s: other, t_info:%x\n", __func__, t_info);
+ /* for operands and prefix-unary operators, attach them
+ * to last node */
+ vn = cn;
+ cn = vn->r.n = new_node(t_info);
+ cn->a.n = vn;
- case TC_NUMBER:
- case TC_STRING:
- debug_printf_parse("%s: TC_NUMBER | TC_STRING\n", __func__);
- cn->info = OC_VAR;
- v = cn->l.v = xzalloc(sizeof(var));
- if (tc & TC_NUMBER)
- setvar_i(v, t_double);
- else {
- setvar_s(v, t_string);
- xtc &= ~TC_UOPPOST; /* "str"++ is not allowed */
- }
- break;
+ expected_tc = TS_OPERAND | TS_UOPPRE | TC_REGEXP;
+ if (t_info == TI_PREINC || t_info == TI_PREDEC)
+ expected_tc = TS_LVALUE | TC_UOPPRE1;
- case TC_REGEXP:
- debug_printf_parse("%s: TC_REGEXP\n", __func__);
- mk_re_node(t_string, cn, xzalloc(sizeof(regex_t)*2));
- break;
+ if (!(tc & (TS_OPERAND | TC_REGEXP)))
+ continue;
- case TC_FUNCTION:
- debug_printf_parse("%s: TC_FUNCTION\n", __func__);
- cn->info = OC_FUNC;
- cn->r.f = newfunc(t_string);
- cn->l.n = condition();
- break;
+ debug_printf_parse("%s: TS_OPERAND | TC_REGEXP\n", __func__);
+ expected_tc = TS_UOPPRE | TC_UOPPOST | TS_BINOP | TS_OPERAND | term_tc;
+ /* one should be very careful with switch on tclass -
+ * only simple tclasses should be used (TC_xyz, not TS_xyz) */
+ switch (tc) {
+ case TC_VARIABLE:
+ case TC_ARRAY:
+ debug_printf_parse("%s: TC_VARIABLE | TC_ARRAY\n", __func__);
+ cn->info = OC_VAR;
+ v = hash_search(ahash, t_string);
+ if (v != NULL) {
+ cn->info = OC_FNARG;
+ cn->l.aidx = v->x.aidx;
+ } else {
+ cn->l.v = newvar(t_string);
+ }
+ if (tc & TC_ARRAY) {
+ cn->info |= xS;
+ cn->r.n = parse_expr(TC_ARRTERM);
+ }
+ break;
- case TC_SEQSTART:
- debug_printf_parse("%s: TC_SEQSTART\n", __func__);
- cn = vn->r.n = parse_expr(TC_SEQTERM);
- if (!cn)
- syntax_error("Empty sequence");
- cn->a.n = vn;
- break;
+ case TC_NUMBER:
+ case TC_STRING:
+ debug_printf_parse("%s: TC_NUMBER | TC_STRING\n", __func__);
+ cn->info = OC_VAR;
+ v = cn->l.v = xzalloc(sizeof(var));
+ if (tc & TC_NUMBER)
+ setvar_i(v, t_double);
+ else {
+ setvar_s(v, t_string);
+ expected_tc &= ~TC_UOPPOST; /* "str"++ is not allowed */
+ }
+ break;
- case TC_GETLINE:
- debug_printf_parse("%s: TC_GETLINE\n", __func__);
- glptr = cn;
- xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
- break;
+ case TC_REGEXP:
+ debug_printf_parse("%s: TC_REGEXP\n", __func__);
+ mk_re_node(t_string, cn, xzalloc(sizeof(regex_t)*2));
+ break;
- case TC_BUILTIN:
- debug_printf_parse("%s: TC_BUILTIN\n", __func__);
- cn->l.n = condition();
- break;
+ case TC_FUNCTION:
+ debug_printf_parse("%s: TC_FUNCTION\n", __func__);
+ cn->info = OC_FUNC;
+ cn->r.f = newfunc(t_string);
+ cn->l.n = parse_expr(TC_RPAREN);
+ break;
- case TC_LENGTH:
- debug_printf_parse("%s: TC_LENGTH\n", __func__);
- next_token(TC_SEQSTART /* length(...) */
- | TC_OPTERM /* length; (or newline)*/
- | TC_GRPTERM /* length } */
- | TC_BINOPX /* length <op> NUM */
- | TC_COMMA /* print length, 1 */
- );
- rollback_token();
- if (t_tclass & TC_SEQSTART) {
- /* It was a "(" token. Handle just like TC_BUILTIN */
- cn->l.n = condition();
- }
- break;
- }
+ case TC_LPAREN:
+ debug_printf_parse("%s: TC_LPAREN\n", __func__);
+ cn = vn->r.n = parse_expr(TC_RPAREN);
+ if (!cn)
+ syntax_error("Empty sequence");
+ cn->a.n = vn;
+ break;
+
+ case TC_GETLINE:
+ debug_printf_parse("%s: TC_GETLINE\n", __func__);
+ glptr = cn;
+ expected_tc = TS_OPERAND | TS_UOPPRE | TS_BINOP | term_tc;
+ break;
+
+ case TC_BUILTIN:
+ debug_printf_parse("%s: TC_BUILTIN\n", __func__);
+ cn->l.n = parse_lrparen_list();
+ break;
+
+ case TC_LENGTH:
+ debug_printf_parse("%s: TC_LENGTH\n", __func__);
+ tc = next_token(TC_LPAREN /* length(...) */
+ | TC_SEMICOL /* length; */
+ | TC_NEWLINE /* length<newline> */
+ | TC_RBRACE /* length } */
+ | TC_BINOPX /* length <op> NUM */
+ | TC_COMMA /* print length, 1 */
+ );
+ if (tc != TC_LPAREN)
+ rollback_token();
+ else {
+ /* It was a "(" token. Handle just like TC_BUILTIN */
+ cn->l.n = parse_expr(TC_RPAREN);
}
+ break;
}
- }
+ } /* while() */
debug_printf_parse("%s() returns %p\n", __func__, sn.r.n);
return sn.r.n;
@@ -1430,7 +1513,7 @@ static node *chain_node(uint32_t info)
if (seq->programname != g_progname) {
seq->programname = g_progname;
n = chain_node(OC_NEWSOURCE);
- n->l.new_progname = xstrdup(g_progname);
+ n->l.new_progname = g_progname;
}
n = seq->last;
@@ -1446,14 +1529,16 @@ static void chain_expr(uint32_t info)
n = chain_node(info);
- n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM);
+ n->l.n = parse_expr(TC_SEMICOL | TC_NEWLINE | TC_RBRACE);
if ((info & OF_REQUIRED) && !n->l.n)
syntax_error(EMSG_TOO_FEW_ARGS);
- if (t_tclass & TC_GRPTERM)
+ if (t_tclass & TC_RBRACE)
rollback_token();
}
+static void chain_group(void);
+
static node *chain_loop(node *nn)
{
node *n, *n2, *save_brk, *save_cont;
@@ -1477,207 +1562,284 @@ static node *chain_loop(node *nn)
return n;
}
+static void chain_until_rbrace(void)
+{
+ uint32_t tc;
+ while ((tc = next_token(TS_GRPSEQ | TC_RBRACE)) != TC_RBRACE) {
+ debug_printf_parse("%s: !TC_RBRACE\n", __func__);
+ if (tc == TC_NEWLINE)
+ continue;
+ rollback_token();
+ chain_group();
+ }
+ debug_printf_parse("%s: TC_RBRACE\n", __func__);
+}
+
/* parse group and attach it to chain */
static void chain_group(void)
{
- uint32_t c;
+ uint32_t tc;
node *n, *n2, *n3;
do {
- c = next_token(TC_GRPSEQ);
- } while (c & TC_NEWLINE);
-
- if (c & TC_GRPSTART) {
- debug_printf_parse("%s: TC_GRPSTART\n", __func__);
- while (next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) {
- debug_printf_parse("%s: !TC_GRPTERM\n", __func__);
- if (t_tclass & TC_NEWLINE)
- continue;
- rollback_token();
- chain_group();
- }
- debug_printf_parse("%s: TC_GRPTERM\n", __func__);
- } else if (c & (TC_OPSEQ | TC_OPTERM)) {
- debug_printf_parse("%s: TC_OPSEQ | TC_OPTERM\n", __func__);
+ tc = next_token(TS_GRPSEQ);
+ } while (tc == TC_NEWLINE);
+
+ if (tc == TC_LBRACE) {
+ debug_printf_parse("%s: TC_LBRACE\n", __func__);
+ chain_until_rbrace();
+ return;
+ }
+ if (tc & (TS_OPSEQ | TC_SEMICOL)) {
+ debug_printf_parse("%s: TS_OPSEQ | TC_SEMICOL\n", __func__);
rollback_token();
chain_expr(OC_EXEC | Vx);
- } else {
- /* TC_STATEMNT */
- debug_printf_parse("%s: TC_STATEMNT(?)\n", __func__);
- switch (t_info & OPCLSMASK) {
- case ST_IF:
- debug_printf_parse("%s: ST_IF\n", __func__);
- n = chain_node(OC_BR | Vx);
- n->l.n = condition();
+ return;
+ }
+
+ /* TS_STATEMNT */
+ debug_printf_parse("%s: TS_STATEMNT(?)\n", __func__);
+ switch (t_info & OPCLSMASK) {
+ case ST_IF:
+ debug_printf_parse("%s: ST_IF\n", __func__);
+ n = chain_node(OC_BR | Vx);
+ n->l.n = parse_lrparen_list();
+ chain_group();
+ n2 = chain_node(OC_EXEC);
+ n->r.n = seq->last;
+ if (next_token(TS_GRPSEQ | TC_RBRACE | TC_ELSE) == TC_ELSE) {
chain_group();
- n2 = chain_node(OC_EXEC);
- n->r.n = seq->last;
- if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE) == TC_ELSE) {
- chain_group();
- n2->a.n = seq->last;
- } else {
- rollback_token();
- }
- break;
+ n2->a.n = seq->last;
+ } else {
+ rollback_token();
+ }
+ break;
- case ST_WHILE:
- debug_printf_parse("%s: ST_WHILE\n", __func__);
- n2 = condition();
- n = chain_loop(NULL);
- n->l.n = n2;
- break;
+ case ST_WHILE:
+ debug_printf_parse("%s: ST_WHILE\n", __func__);
+ n2 = parse_lrparen_list();
+ n = chain_loop(NULL);
+ n->l.n = n2;
+ break;
- case ST_DO:
- debug_printf_parse("%s: ST_DO\n", __func__);
- n2 = chain_node(OC_EXEC);
- n = chain_loop(NULL);
- n2->a.n = n->a.n;
- next_token(TC_WHILE);
- n->l.n = condition();
- break;
+ case ST_DO:
+ debug_printf_parse("%s: ST_DO\n", __func__);
+ n2 = chain_node(OC_EXEC);
+ n = chain_loop(NULL);
+ n2->a.n = n->a.n;
+ next_token(TC_WHILE);
+ n->l.n = parse_lrparen_list();
+ break;
- case ST_FOR:
- debug_printf_parse("%s: ST_FOR\n", __func__);
- next_token(TC_SEQSTART);
- n2 = parse_expr(TC_SEMICOL | TC_SEQTERM);
- if (t_tclass & TC_SEQTERM) { /* for-in */
- if (!n2 || (n2->info & OPCLSMASK) != OC_IN)
- syntax_error(EMSG_UNEXP_TOKEN);
- n = chain_node(OC_WALKINIT | VV);
- n->l.n = n2->l.n;
- n->r.n = n2->r.n;
- n = chain_loop(NULL);
- n->info = OC_WALKNEXT | Vx;
- n->l.n = n2->l.n;
- } else { /* for (;;) */
- n = chain_node(OC_EXEC | Vx);
- n->l.n = n2;
- n2 = parse_expr(TC_SEMICOL);
- n3 = parse_expr(TC_SEQTERM);
- n = chain_loop(n3);
- n->l.n = n2;
- if (!n2)
- n->info = OC_EXEC;
- }
- break;
+ case ST_FOR:
+ debug_printf_parse("%s: ST_FOR\n", __func__);
+ next_token(TC_LPAREN);
+ n2 = parse_expr(TC_SEMICOL | TC_RPAREN);
+ if (t_tclass & TC_RPAREN) { /* for (I in ARRAY) */
+ if (!n2 || n2->info != TI_IN)
+ syntax_error(EMSG_UNEXP_TOKEN);
+ n = chain_node(OC_WALKINIT | VV);
+ n->l.n = n2->l.n;
+ n->r.n = n2->r.n;
+ n = chain_loop(NULL);
+ n->info = OC_WALKNEXT | Vx;
+ n->l.n = n2->l.n;
+ } else { /* for (;;) */
+ n = chain_node(OC_EXEC | Vx);
+ n->l.n = n2;
+ n2 = parse_expr(TC_SEMICOL);
+ n3 = parse_expr(TC_RPAREN);
+ n = chain_loop(n3);
+ n->l.n = n2;
+ if (!n2)
+ n->info = OC_EXEC;
+ }
+ break;
- case OC_PRINT:
- case OC_PRINTF:
- debug_printf_parse("%s: OC_PRINT[F]\n", __func__);
- n = chain_node(t_info);
- n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM);
- if (t_tclass & TC_OUTRDR) {
- n->info |= t_info;
- n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM);
- }
- if (t_tclass & TC_GRPTERM)
- rollback_token();
- break;
+ case OC_PRINT:
+ case OC_PRINTF:
+ debug_printf_parse("%s: OC_PRINT[F]\n", __func__);
+ n = chain_node(t_info);
+ n->l.n = parse_expr(TC_SEMICOL | TC_NEWLINE | TC_OUTRDR | TC_RBRACE);
+ if (t_tclass & TC_OUTRDR) {
+ n->info |= t_info;
+ n->r.n = parse_expr(TC_SEMICOL | TC_NEWLINE | TC_RBRACE);
+ }
+ if (t_tclass & TC_RBRACE)
+ rollback_token();
+ break;
- case OC_BREAK:
- debug_printf_parse("%s: OC_BREAK\n", __func__);
- n = chain_node(OC_EXEC);
- n->a.n = break_ptr;
- chain_expr(t_info);
- break;
+ case OC_BREAK:
+ debug_printf_parse("%s: OC_BREAK\n", __func__);
+ n = chain_node(OC_EXEC);
+ if (!break_ptr)
+ syntax_error("'break' not in a loop");
+ n->a.n = break_ptr;
+ chain_expr(t_info);
+ break;
- case OC_CONTINUE:
- debug_printf_parse("%s: OC_CONTINUE\n", __func__);
- n = chain_node(OC_EXEC);
- n->a.n = continue_ptr;
- chain_expr(t_info);
- break;
+ case OC_CONTINUE:
+ debug_printf_parse("%s: OC_CONTINUE\n", __func__);
+ n = chain_node(OC_EXEC);
+ if (!continue_ptr)
+ syntax_error("'continue' not in a loop");
+ n->a.n = continue_ptr;
+ chain_expr(t_info);
+ break;
- /* delete, next, nextfile, return, exit */
- default:
- debug_printf_parse("%s: default\n", __func__);
- chain_expr(t_info);
- }
+ /* delete, next, nextfile, return, exit */
+ default:
+ debug_printf_parse("%s: default\n", __func__);
+ chain_expr(t_info);
}
}
static void parse_program(char *p)
{
- uint32_t tclass;
- node *cn;
- func *f;
- var *v;
+ debug_printf_parse("%s()\n", __func__);
g_pos = p;
t_lineno = 1;
- while ((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
- TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
+ for (;;) {
+ uint32_t tclass;
- if (tclass & TC_OPTERM) {
- debug_printf_parse("%s: TC_OPTERM\n", __func__);
+ tclass = next_token(TS_OPSEQ | TC_LBRACE | TC_BEGIN | TC_END | TC_FUNCDECL
+ | TC_EOF | TC_NEWLINE /* but not TC_SEMICOL */);
+ got_tok:
+ if (tclass == TC_EOF) {
+ debug_printf_parse("%s: TC_EOF\n", __func__);
+ break;
+ }
+ if (tclass == TC_NEWLINE) {
+ debug_printf_parse("%s: TC_NEWLINE\n", __func__);
continue;
}
-
- seq = &mainseq;
- if (tclass & TC_BEGIN) {
+ if (tclass == TC_BEGIN) {
debug_printf_parse("%s: TC_BEGIN\n", __func__);
seq = &beginseq;
- chain_group();
- } else if (tclass & TC_END) {
+ /* ensure there is no newline between BEGIN and { */
+ next_token(TC_LBRACE);
+ chain_until_rbrace();
+ goto next_tok;
+ }
+ if (tclass == TC_END) {
debug_printf_parse("%s: TC_END\n", __func__);
seq = &endseq;
- chain_group();
- } else if (tclass & TC_FUNCDECL) {
+ /* ensure there is no newline between END and { */
+ next_token(TC_LBRACE);
+ chain_until_rbrace();
+ goto next_tok;
+ }
+ if (tclass == TC_FUNCDECL) {
+ func *f;
+
debug_printf_parse("%s: TC_FUNCDECL\n", __func__);
next_token(TC_FUNCTION);
- g_pos++;
f = newfunc(t_string);
- f->body.first = NULL;
- f->nargs = 0;
- /* Match func arg list: a comma sep list of >= 0 args, and a close paren */
- while (next_token(TC_VARIABLE | TC_SEQTERM | TC_COMMA)) {
- /* Either an empty arg list, or trailing comma from prev iter
- * must be followed by an arg */
- if (f->nargs == 0 && t_tclass == TC_SEQTERM)
- break;
-
- /* TC_SEQSTART/TC_COMMA must be followed by TC_VARIABLE */
- if (t_tclass != TC_VARIABLE)
+ if (f->defined)
+ syntax_error("Duplicate function");
+ f->defined = 1;
+ //f->body.first = NULL; - already is
+ //f->nargs = 0; - already is
+ /* func arg list: comma sep list of args, and a close paren */
+ for (;;) {
+ var *v;
+ if (next_token(TC_VARIABLE | TC_RPAREN) == TC_RPAREN) {
+ if (f->nargs == 0)
+ break; /* func() is ok */
+ /* func(a,) is not ok */
syntax_error(EMSG_UNEXP_TOKEN);
-
+ }
v = findvar(ahash, t_string);
v->x.aidx = f->nargs++;
-
/* Arg followed either by end of arg list or 1 comma */
- if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
+ if (next_token(TC_COMMA | TC_RPAREN) == TC_RPAREN)
break;
- if (t_tclass != TC_COMMA)
- syntax_error(EMSG_UNEXP_TOKEN);
+ /* it was a comma, we ate it */
}
seq = &f->body;
- chain_group();
- clear_array(ahash);
- } else if (tclass & TC_OPSEQ) {
- debug_printf_parse("%s: TC_OPSEQ\n", __func__);
+ /* ensure there is { after "func F(...)" - but newlines are allowed */
+ while (next_token(TC_LBRACE | TC_NEWLINE) == TC_NEWLINE)
+ continue;
+ chain_until_rbrace();
+ hash_clear(ahash);
+ goto next_tok;
+ }
+ seq = &mainseq;
+ if (tclass & TS_OPSEQ) {
+ node *cn;
+
+ debug_printf_parse("%s: TS_OPSEQ\n", __func__);
rollback_token();
cn = chain_node(OC_TEST);
- cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART);
- if (t_tclass & TC_GRPSTART) {
- debug_printf_parse("%s: TC_GRPSTART\n", __func__);
- rollback_token();
- chain_group();
+ cn->l.n = parse_expr(TC_SEMICOL | TC_NEWLINE | TC_EOF | TC_LBRACE);
+ if (t_tclass == TC_LBRACE) {
+ debug_printf_parse("%s: TC_LBRACE\n", __func__);
+ chain_until_rbrace();
} else {
- debug_printf_parse("%s: !TC_GRPSTART\n", __func__);
+ /* no action, assume default "{ print }" */
+ debug_printf_parse("%s: !TC_LBRACE\n", __func__);
chain_node(OC_PRINT);
}
cn->r.n = mainseq.last;
- } else /* if (tclass & TC_GRPSTART) */ {
- debug_printf_parse("%s: TC_GRPSTART(?)\n", __func__);
- rollback_token();
- chain_group();
+ goto next_tok;
}
- }
- debug_printf_parse("%s: TC_EOF\n", __func__);
+ /* tclass == TC_LBRACE */
+ debug_printf_parse("%s: TC_LBRACE(?)\n", __func__);
+ chain_until_rbrace();
+ next_tok:
+ /* Same as next_token() at the top of the loop, + TC_SEMICOL */
+ tclass = next_token(TS_OPSEQ | TC_LBRACE | TC_BEGIN | TC_END | TC_FUNCDECL
+ | TC_EOF | TC_NEWLINE | TC_SEMICOL);
+ /* gawk allows many newlines, but does not allow more than one semicolon:
+ * BEGIN {...}<newline>;<newline>;
+ * would complain "each rule must have a pattern or an action part".
+ * Same message for
+ * ; BEGIN {...}
+ */
+ if (tclass != TC_SEMICOL)
+ goto got_tok; /* use this token */
+ /* else: loop back - ate the semicolon, get and use _next_ token */
+ } /* for (;;) */
}
-
/* -------- program execution part -------- */
+/* temporary variables allocator */
+static var *nvalloc(int sz)
+{
+ return xzalloc(sz * sizeof(var));
+}
+
+static void nvfree(var *v, int sz)
+{
+ var *p = v;
+
+ while (--sz >= 0) {
+ if ((p->type & (VF_ARRAY | VF_CHILD)) == VF_ARRAY) {
+ clear_array(iamarray(p));
+ free(p->x.array->items);
+ free(p->x.array);
+ }
+ if (p->type & VF_WALK) {
+ walker_list *n;
+ walker_list *w = p->x.walker;
+ debug_printf_walker("nvfree: freeing walker @%p\n", &p->x.walker);
+ p->x.walker = NULL;
+ while (w) {
+ n = w->prev;
+ debug_printf_walker(" free(%p)\n", w);
+ free(w);
+ w = n;
+ }
+ }
+ clrvar(p);
+ p++;
+ }
+
+ free(v);
+}
+
static node *mk_splitter(const char *s, tsplitter *spl)
{
regex_t *re, *ire;
@@ -1686,7 +1848,7 @@ static node *mk_splitter(const char *s, tsplitter *spl)
re = &spl->re[0];
ire = &spl->re[1];
n = &spl->n;
- if ((n->info & OPCLSMASK) == OC_REGEXP) {
+ if (n->info == TI_REGEXP) {
regfree(re);
regfree(ire); // TODO: nuke ire, use re+1?
}
@@ -1699,21 +1861,28 @@ static node *mk_splitter(const char *s, tsplitter *spl)
return n;
}
-/* use node as a regular expression. Supplied with node ptr and regex_t
+static var *evaluate(node *, var *);
+
+/* Use node as a regular expression. Supplied with node ptr and regex_t
* storage space. Return ptr to regex (if result points to preg, it should
- * be later regfree'd manually
+ * be later regfree'd manually).
*/
static regex_t *as_regex(node *op, regex_t *preg)
{
int cflags;
- var *v;
const char *s;
- if ((op->info & OPCLSMASK) == OC_REGEXP) {
+ if (op->info == TI_REGEXP) {
return icase ? op->r.ire : op->l.re;
}
- v = nvalloc(1);
- s = getvar_s(evaluate(op, v));
+
+ //tmpvar = nvalloc(1);
+#define TMPVAR (&G.as_regex__tmpvar)
+ // We use a single "static" tmpvar (instead of on-stack or malloced one)
+ // to decrease memory consumption in deeply-recursive awk programs.
+ // The rule to work safely is to never call evaluate() while our static
+ // TMPVAR's value is still needed.
+ s = getvar_s(evaluate(op, TMPVAR));
cflags = icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED;
/* Testcase where REG_EXTENDED fails (unpaired '{'):
@@ -1725,7 +1894,8 @@ static regex_t *as_regex(node *op, regex_t *preg)
cflags &= ~REG_EXTENDED;
xregcomp(preg, s, cflags);
}
- nvfree(v);
+ //nvfree(tmpvar, 1);
+#undef TMPVAR
return preg;
}
@@ -1745,12 +1915,22 @@ static char* qrealloc(char *b, int n, int *size)
/* resize field storage space */
static void fsrealloc(int size)
{
- int i;
+ int i, newsize;
if (size >= maxfields) {
+ /* Sanity cap, easier than catering for overflows */
+ if (size > 0xffffff)
+ bb_die_memory_exhausted();
+
i = maxfields;
maxfields = size + 16;
- Fields = xrealloc(Fields, maxfields * sizeof(Fields[0]));
+
+ newsize = maxfields * sizeof(Fields[0]);
+ debug_printf_eval("fsrealloc: xrealloc(%p, %u)\n", Fields, newsize);
+ Fields = xrealloc(Fields, newsize);
+ debug_printf_eval("fsrealloc: Fields=%p..%p\n", Fields, (char*)Fields + newsize - 1);
+ /* ^^^ did Fields[] move? debug aid for L.v getting "upstaged" by R.v in evaluate() */
+
for (; i < maxfields; i++) {
Fields[i].type = VF_SPECIAL;
Fields[i].string = NULL;
@@ -1802,13 +1982,13 @@ static int awk_split(const char *s, node *spl, char **slist)
c[2] = '\n';
n = 0;
- if ((spl->info & OPCLSMASK) == OC_REGEXP) { /* regex split */
+ if (spl->info == TI_REGEXP) { /* regex split */
if (!*s)
return n; /* "": zero fields */
n++; /* at least one field will be there */
do {
int l;
- regmatch_t pmatch[2]; // TODO: why [2]? [1] is enough...
+ regmatch_t pmatch[1];
l = strcspn(s, c+2); /* len till next NUL or \n */
if (regexec1_nonempty(icase ? spl->r.ire : spl->l.re, s, pmatch) == 0
@@ -1969,7 +2149,7 @@ static node *nextarg(node **pn)
node *n;
n = *pn;
- if (n && (n->info & OPCLSMASK) == OC_COMMA) {
+ if (n && n->info == TI_COMMA) {
*pn = n->r.n;
n = n->l.n;
} else {
@@ -2000,8 +2180,7 @@ static void hashwalk_init(var *v, xhash *array)
for (i = 0; i < array->csize; i++) {
hi = array->items[i];
while (hi) {
- strcpy(w->end, hi->name);
- nextword(&w->end);
+ w->end = stpcpy(w->end, hi->name) + 1;
hi = hi->next;
}
}
@@ -2027,15 +2206,18 @@ static int hashwalk_next(var *v)
/* evaluate node, return 1 when result is true, 0 otherwise */
static int ptest(node *pattern)
{
- /* ptest__v is "static": to save stack space? */
- return istrue(evaluate(pattern, &G.ptest__v));
+ // We use a single "static" tmpvar (instead of on-stack or malloced one)
+ // to decrease memory consumption in deeply-recursive awk programs.
+ // The rule to work safely is to never call evaluate() while our static
+ // TMPVAR's value is still needed.
+ return istrue(evaluate(pattern, &G.ptest__tmpvar));
}
/* read next record from stream rsm into a variable v */
static int awk_getline(rstream *rsm, var *v)
{
char *b;
- regmatch_t pmatch[2]; // TODO: why [2]? [1] is enough...
+ regmatch_t pmatch[1];
int size, a, p, pp = 0;
int fd, so, eo, r, rp;
char c, *m, *s;
@@ -2061,7 +2243,7 @@ static int awk_getline(rstream *rsm, var *v)
so = eo = p;
r = 1;
if (p > 0) {
- if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) {
+ if (rsplitter.n.info == TI_REGEXP) {
if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re,
b, 1, pmatch, 0) == 0) {
so = pmatch[0].rm_so;
@@ -2133,45 +2315,36 @@ static int awk_getline(rstream *rsm, var *v)
return r;
}
-static int fmt_num(char *b, int size, const char *format, double n, int int_as_int)
-{
- int r = 0;
- char c;
- const char *s = format;
-
- if (int_as_int && n == (long long)n) {
- r = snprintf(b, size, "%lld", (long long)n);
- } else {
- do { c = *s; } while (c && *++s);
- if (strchr("diouxX", c)) {
- r = snprintf(b, size, format, (int)n);
- } else if (strchr("eEfgG", c)) {
- r = snprintf(b, size, format, n);
- } else {
- syntax_error(EMSG_INV_FMT);
- }
- }
- return r;
-}
-
/* formatted output into an allocated buffer, return ptr to buffer */
#if !ENABLE_FEATURE_AWK_GNU_EXTENSIONS
# define awk_printf(a, b) awk_printf(a)
#endif
-static char *awk_printf(node *n, int *len)
+static char *awk_printf(node *n, size_t *len)
{
- char *b = NULL;
- char *fmt, *s, *f;
- const char *s1;
- int i, j, incr, bsize;
- char c, c1;
- var *v, *arg;
-
- v = nvalloc(1);
- fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v)));
-
+ char *b;
+ char *fmt, *f;
+ size_t i;
+
+ //tmpvar = nvalloc(1);
+#define TMPVAR (&G.awk_printf__tmpvar)
+ // We use a single "static" tmpvar (instead of on-stack or malloced one)
+ // to decrease memory consumption in deeply-recursive awk programs.
+ // The rule to work safely is to never call evaluate() while our static
+ // TMPVAR's value is still needed.
+ fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), TMPVAR)));
+ // ^^^^^^^^^ here we immediately strdup() the value, so the later call
+ // to evaluate() potentially recursing into another awk_printf() can't
+ // mangle the value.
+
+ b = NULL;
i = 0;
- while (*f) {
+ while (*f) { /* "print one format spec" loop */
+ char *s;
+ char c;
+ char sv;
+ var *arg;
+ size_t slen;
+
s = f;
while (*f && (*f != '%' || *++f == '%'))
f++;
@@ -2180,38 +2353,64 @@ static char *awk_printf(node *n, int *len)
syntax_error("%*x formats are not supported");
f++;
}
-
- incr = (f - s) + MAXVARFMT;
- b = qrealloc(b, incr + i, &bsize);
c = *f;
- if (c != '\0')
- f++;
- c1 = *f;
+ if (!c) {
+ /* Tail of fmt with no percent chars,
+ * or "....%" (percent seen, but no format specifier char found)
+ */
+ slen = strlen(s);
+ goto tail;
+ }
+ sv = *++f;
*f = '\0';
- arg = evaluate(nextarg(&n), v);
-
- j = i;
- if (c == 'c' || !c) {
- i += sprintf(b+i, s, is_numeric(arg) ?
- (char)getvar_i(arg) : *getvar_s(arg));
- } else if (c == 's') {
- s1 = getvar_s(arg);
- b = qrealloc(b, incr+i+strlen(s1), &bsize);
- i += sprintf(b+i, s, s1);
+ arg = evaluate(nextarg(&n), TMPVAR);
+
+ /* Result can be arbitrarily long. Example:
+ * printf "%99999s", "BOOM"
+ */
+ if (c == 'c') {
+ char cc = is_numeric(arg) ? getvar_i(arg) : *getvar_s(arg);
+ char *r = xasprintf(s, cc ? cc : '^' /* else strlen will be wrong */);
+ slen = strlen(r);
+ if (cc == '\0') /* if cc is NUL, re-format the string with it */
+ sprintf(r, s, cc);
+ s = r;
} else {
- i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE);
+ if (c == 's') {
+ s = xasprintf(s, getvar_s(arg));
+ } else {
+ double d = getvar_i(arg);
+ if (strchr("diouxX", c)) {
+//TODO: make it wider here (%x -> %llx etc)?
+ s = xasprintf(s, (int)d);
+ } else if (strchr("eEfFgGaA", c)) {
+ s = xasprintf(s, d);
+ } else {
+ syntax_error(EMSG_INV_FMT);
+ }
+ }
+ slen = strlen(s);
}
- *f = c1;
+ *f = sv;
- /* if there was an error while sprintf, return value is negative */
- if (i < j)
- i = j;
+ if (i == 0) {
+ b = s;
+ i = slen;
+ continue;
+ }
+ tail:
+ b = xrealloc(b, i + slen + 1);
+ strcpy(b + i, s);
+ i += slen;
+ if (!c) /* tail? */
+ break;
+ free(s);
}
free(fmt);
- nvfree(v);
- b = xrealloc(b, i + 1);
- b[i] = '\0';
+ //nvfree(tmpvar, 1);
+#undef TMPVAR
+
#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
if (len)
*len = i;
@@ -2345,33 +2544,59 @@ static NOINLINE int do_mktime(const char *ds)
return mktime(&then);
}
+/* Reduce stack usage in exec_builtin() by keeping match() code separate */
+static NOINLINE var *do_match(node *an1, const char *as0)
+{
+ regmatch_t pmatch[1];
+ regex_t sreg, *re;
+ int n, start, len;
+
+ re = as_regex(an1, &sreg);
+ n = regexec(re, as0, 1, pmatch, 0);
+ if (re == &sreg)
+ regfree(re);
+ start = 0;
+ len = -1;
+ if (n == 0) {
+ start = pmatch[0].rm_so + 1;
+ len = pmatch[0].rm_eo - pmatch[0].rm_so;
+ }
+ setvar_i(newvar("RLENGTH"), len);
+ return setvar_i(newvar("RSTART"), start);
+}
+
+/* Reduce stack usage in evaluate() by keeping builtins' code separate */
static NOINLINE var *exec_builtin(node *op, var *res)
{
#define tspl (G.exec_builtin__tspl)
- var *tv;
+ var *tmpvars;
node *an[4];
var *av[4];
const char *as[4];
- regmatch_t pmatch[2];
- regex_t sreg, *re;
node *spl;
uint32_t isr, info;
int nargs;
time_t tt;
int i, l, ll, n;
- tv = nvalloc(4);
+ tmpvars = nvalloc(4);
+#define TMPVAR0 (tmpvars)
+#define TMPVAR1 (tmpvars + 1)
+#define TMPVAR2 (tmpvars + 2)
+#define TMPVAR3 (tmpvars + 3)
+#define TMPVAR(i) (tmpvars + (i))
isr = info = op->info;
op = op->l.n;
av[2] = av[3] = NULL;
for (i = 0; i < 4 && op; i++) {
an[i] = nextarg(&op);
- if (isr & 0x09000000)
- av[i] = evaluate(an[i], &tv[i]);
- if (isr & 0x08000000)
- as[i] = getvar_s(av[i]);
+ if (isr & 0x09000000) {
+ av[i] = evaluate(an[i], TMPVAR(i));
+ if (isr & 0x08000000)
+ as[i] = getvar_s(av[i]);
+ }
isr >>= 1;
}
@@ -2393,8 +2618,8 @@ static NOINLINE var *exec_builtin(node *op, var *res)
char *s, *s1;
if (nargs > 2) {
- spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ?
- an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl);
+ spl = (an[2]->info == TI_REGEXP) ? an[2]
+ : mk_splitter(getvar_s(evaluate(an[2], TMPVAR2)), &tspl);
} else {
spl = &fsplitter.n;
}
@@ -2508,20 +2733,7 @@ static NOINLINE var *exec_builtin(node *op, var *res)
break;
case B_ma:
- re = as_regex(an[1], &sreg);
- n = regexec(re, as[0], 1, pmatch, 0);
- if (n == 0) {
- pmatch[0].rm_so++;
- pmatch[0].rm_eo++;
- } else {
- pmatch[0].rm_so = 0;
- pmatch[0].rm_eo = -1;
- }
- setvar_i(newvar("RSTART"), pmatch[0].rm_so);
- setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so);
- setvar_i(res, pmatch[0].rm_so);
- if (re == &sreg)
- regfree(re);
+ res = do_match(an[1], as[0]);
break;
case B_ge:
@@ -2537,14 +2749,79 @@ static NOINLINE var *exec_builtin(node *op, var *res)
break;
}
- nvfree(tv);
+ nvfree(tmpvars, 4);
+#undef TMPVAR0
+#undef TMPVAR1
+#undef TMPVAR2
+#undef TMPVAR3
+#undef TMPVAR
+
return res;
#undef tspl
}
+/* if expr looks like "var=value", perform assignment and return 1,
+ * otherwise return 0 */
+static int is_assignment(const char *expr)
+{
+ char *exprc, *val;
+
+ val = (char*)endofname(expr);
+ if (val == (char*)expr || *val != '=') {
+ return FALSE;
+ }
+
+ exprc = xstrdup(expr);
+ val = exprc + (val - expr);
+ *val++ = '\0';
+
+ unescape_string_in_place(val);
+ setvar_u(newvar(exprc), val);
+ free(exprc);
+ return TRUE;
+}
+
+/* switch to next input file */
+static rstream *next_input_file(void)
+{
+#define rsm (G.next_input_file__rsm)
+#define files_happen (G.next_input_file__files_happen)
+
+ const char *fname, *ind;
+
+ if (rsm.F)
+ fclose(rsm.F);
+ rsm.F = NULL;
+ rsm.pos = rsm.adv = 0;
+
+ for (;;) {
+ if (getvar_i(intvar[ARGIND])+1 >= getvar_i(intvar[ARGC])) {
+ if (files_happen)
+ return NULL;
+ fname = "-";
+ rsm.F = stdin;
+ break;
+ }
+ ind = getvar_s(incvar(intvar[ARGIND]));
+ fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
+ if (fname && *fname && !is_assignment(fname)) {
+ rsm.F = xfopen_stdin(fname);
+ break;
+ }
+ }
+
+ files_happen = TRUE;
+ setvar_s(intvar[FILENAME], fname);
+ return &rsm;
+#undef rsm
+#undef files_happen
+}
+
/*
* Evaluate node - the heart of the program. Supplied with subtree
- * and place where to store result. returns ptr to result.
+ * and "res" variable to assign the result to if we evaluate an expression.
+ * If node refers to e.g. a variable or a field, no assignment happens.
+ * Return ptr to the result (which may or may not be the "res" variable!)
*/
#define XC(n) ((n) >> 8)
@@ -2556,14 +2833,16 @@ static var *evaluate(node *op, var *res)
#define seed (G.evaluate__seed)
#define sreg (G.evaluate__sreg)
- var *v1;
+ var *tmpvars;
if (!op)
return setvar_s(res, NULL);
debug_printf_eval("entered %s()\n", __func__);
- v1 = nvalloc(2);
+ tmpvars = nvalloc(2);
+#define TMPVAR0 (tmpvars)
+#define TMPVAR1 (tmpvars + 1)
while (op) {
struct {
@@ -2585,48 +2864,35 @@ static var *evaluate(node *op, var *res)
op1 = op->l.n;
debug_printf_eval("opinfo:%08x opn:%08x\n", opinfo, opn);
- /* "delete" is special:
- * "delete array[var--]" must evaluate index expr only once,
- * must not evaluate it in "execute inevitable things" part.
- */
- if (XC(opinfo & OPCLSMASK) == XC(OC_DELETE)) {
- uint32_t info = op1->info & OPCLSMASK;
- var *v;
-
- debug_printf_eval("DELETE\n");
- if (info == OC_VAR) {
- v = op1->l.v;
- } else if (info == OC_FNARG) {
- v = &fnargs[op1->l.aidx];
- } else {
- syntax_error(EMSG_NOT_ARRAY);
+ /* execute inevitable things */
+ if (opinfo & OF_RES1) {
+ if ((opinfo & OF_REQUIRED) && !op1)
+ syntax_error(EMSG_TOO_FEW_ARGS);
+ L.v = evaluate(op1, TMPVAR0);
+ if (opinfo & OF_STR1) {
+ L.s = getvar_s(L.v);
+ debug_printf_eval("L.s:'%s'\n", L.s);
}
- if (op1->r.n) { /* array ref? */
- const char *s;
- s = getvar_s(evaluate(op1->r.n, v1));
- hash_remove(iamarray(v), s);
- } else {
- clear_array(iamarray(v));
+ if (opinfo & OF_NUM1) {
+ L_d = getvar_i(L.v);
+ debug_printf_eval("L_d:%f\n", L_d);
}
- goto next;
- }
-
- /* execute inevitable things */
- if (opinfo & OF_RES1)
- L.v = evaluate(op1, v1);
- if (opinfo & OF_RES2)
- R.v = evaluate(op->r.n, v1+1);
- if (opinfo & OF_STR1) {
- L.s = getvar_s(L.v);
- debug_printf_eval("L.s:'%s'\n", L.s);
- }
- if (opinfo & OF_STR2) {
- R.s = getvar_s(R.v);
- debug_printf_eval("R.s:'%s'\n", R.s);
}
- if (opinfo & OF_NUM1) {
- L_d = getvar_i(L.v);
- debug_printf_eval("L_d:%f\n", L_d);
+ /* NB: Must get string/numeric values of L (done above)
+ * _before_ evaluate()'ing R.v: if both L and R are $NNNs,
+ * and right one is large, then L.v points to Fields[NNN1],
+ * second evaluate() reallocates and moves (!) Fields[],
+ * R.v points to Fields[NNN2] but L.v now points to freed mem!
+ * (Seen trying to evaluate "$444 $44444")
+ */
+ if (opinfo & OF_RES2) {
+ R.v = evaluate(op->r.n, TMPVAR1);
+ //TODO: L.v may be invalid now, set L.v to NULL to catch bugs?
+ //L.v = NULL;
+ if (opinfo & OF_STR2) {
+ R.s = getvar_s(R.v);
+ debug_printf_eval("R.s:'%s'\n", R.s);
+ }
}
debug_printf_eval("switch(0x%x)\n", XC(opinfo & OPCLSMASK));
@@ -2636,7 +2902,8 @@ static var *evaluate(node *op, var *res)
/* test pattern */
case XC( OC_TEST ):
- if ((op1->info & OPCLSMASK) == OC_COMMA) {
+ debug_printf_eval("TEST\n");
+ if (op1->info == TI_COMMA) {
/* it's range pattern */
if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) {
op->info |= OF_CHECKED;
@@ -2653,27 +2920,33 @@ static var *evaluate(node *op, var *res)
/* just evaluate an expression, also used as unconditional jump */
case XC( OC_EXEC ):
+ debug_printf_eval("EXEC\n");
break;
/* branch, used in if-else and various loops */
case XC( OC_BR ):
+ debug_printf_eval("BR\n");
op = istrue(L.v) ? op->a.n : op->r.n;
break;
/* initialize for-in loop */
case XC( OC_WALKINIT ):
+ debug_printf_eval("WALKINIT\n");
hashwalk_init(L.v, iamarray(R.v));
break;
/* get next array item */
case XC( OC_WALKNEXT ):
+ debug_printf_eval("WALKNEXT\n");
op = hashwalk_next(L.v) ? op->a.n : op->r.n;
break;
case XC( OC_PRINT ):
- case XC( OC_PRINTF ): {
+ debug_printf_eval("PRINT /\n");
+ case XC( OC_PRINTF ):
+ debug_printf_eval("PRINTF\n");
+ {
FILE *F = stdout;
- IF_FEATURE_AWK_GNU_EXTENSIONS(int len;)
if (op->r.n) {
rstream *rsm = newfile(R.s);
@@ -2690,27 +2963,30 @@ static var *evaluate(node *op, var *res)
F = rsm->F;
}
+ /* Can't just check 'opinfo == OC_PRINT' here, parser ORs
+ * additional bits to opinfos of print/printf with redirects
+ */
if ((opinfo & OPCLSMASK) == OC_PRINT) {
if (!op1) {
fputs(getvar_s(intvar[F0]), F);
} else {
- while (op1) {
- var *v = evaluate(nextarg(&op1), v1);
+ for (;;) {
+ var *v = evaluate(nextarg(&op1), TMPVAR0);
if (v->type & VF_NUMBER) {
- fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[OFMT]),
- getvar_i(v), TRUE);
+ fmt_num(getvar_s(intvar[OFMT]),
+ getvar_i(v));
fputs(g_buf, F);
} else {
fputs(getvar_s(v), F);
}
-
- if (op1)
- fputs(getvar_s(intvar[OFS]), F);
+ if (!op1)
+ break;
+ fputs(getvar_s(intvar[OFS]), F);
}
}
fputs(getvar_s(intvar[ORS]), F);
-
- } else { /* OC_PRINTF */
+ } else { /* PRINTF */
+ IF_FEATURE_AWK_GNU_EXTENSIONS(size_t len;)
char *s = awk_printf(op1, &len);
#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
fwrite(s, len, 1, F);
@@ -2723,26 +2999,58 @@ static var *evaluate(node *op, var *res)
break;
}
- /* case XC( OC_DELETE ): - moved to happen before arg evaluation */
+ case XC( OC_DELETE ):
+ debug_printf_eval("DELETE\n");
+ {
+ /* "delete" is special:
+ * "delete array[var--]" must evaluate index expr only once.
+ */
+ uint32_t info = op1->info & OPCLSMASK;
+ var *v;
+
+ if (info == OC_VAR) {
+ v = op1->l.v;
+ } else if (info == OC_FNARG) {
+ v = &fnargs[op1->l.aidx];
+ } else {
+ syntax_error(EMSG_NOT_ARRAY);
+ }
+ if (op1->r.n) { /* array ref? */
+ const char *s;
+ s = getvar_s(evaluate(op1->r.n, TMPVAR0));
+ hash_remove(iamarray(v), s);
+ } else {
+ clear_array(iamarray(v));
+ }
+ break;
+ }
case XC( OC_NEWSOURCE ):
+ debug_printf_eval("NEWSOURCE\n");
g_progname = op->l.new_progname;
break;
case XC( OC_RETURN ):
+ debug_printf_eval("RETURN\n");
copyvar(res, L.v);
break;
case XC( OC_NEXTFILE ):
+ debug_printf_eval("NEXTFILE\n");
nextfile = TRUE;
case XC( OC_NEXT ):
+ debug_printf_eval("NEXT\n");
nextrec = TRUE;
case XC( OC_DONE ):
+ debug_printf_eval("DONE\n");
clrvar(res);
break;
case XC( OC_EXIT ):
- awk_exit(L_d);
+ debug_printf_eval("EXIT\n");
+ if (op1)
+ G.exitcode = (int)L_d;
+ awk_exit();
/* -- recursive node type -- */
@@ -2761,15 +3069,18 @@ static var *evaluate(node *op, var *res)
break;
case XC( OC_IN ):
+ debug_printf_eval("IN\n");
setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0);
break;
case XC( OC_REGEXP ):
+ debug_printf_eval("REGEXP\n");
op1 = op;
L.s = getvar_s(intvar[F0]);
goto re_cont;
case XC( OC_MATCH ):
+ debug_printf_eval("MATCH\n");
op1 = op->r.n;
re_cont:
{
@@ -2784,61 +3095,80 @@ static var *evaluate(node *op, var *res)
case XC( OC_MOVE ):
debug_printf_eval("MOVE\n");
/* if source is a temporary string, jusk relink it to dest */
-//Disabled: if R.v is numeric but happens to have cached R.v->string,
-//then L.v ends up being a string, which is wrong
-// if (R.v == v1+1 && R.v->string) {
-// res = setvar_p(L.v, R.v->string);
-// R.v->string = NULL;
-// } else {
+ if (R.v == TMPVAR1
+ && !(R.v->type & VF_NUMBER)
+ /* Why check !NUMBER? if R.v is a number but has cached R.v->string,
+ * L.v ends up a string, which is wrong */
+ /*&& R.v->string - always not NULL (right?) */
+ ) {
+ res = setvar_p(L.v, R.v->string); /* avoids strdup */
+ R.v->string = NULL;
+ } else {
res = copyvar(L.v, R.v);
-// }
+ }
break;
case XC( OC_TERNARY ):
- if ((op->r.n->info & OPCLSMASK) != OC_COLON)
+ debug_printf_eval("TERNARY\n");
+ if (op->r.n->info != TI_COLON)
syntax_error(EMSG_POSSIBLE_ERROR);
res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
break;
case XC( OC_FUNC ): {
- var *vbeg, *v;
+ var *argvars, *sv_fnargs;
const char *sv_progname;
+ int nargs, i;
- /* The body might be empty, still has to eval the args */
- if (!op->r.n->info && !op->r.f->body.first)
+ debug_printf_eval("FUNC\n");
+
+ if (!op->r.f->defined)
syntax_error(EMSG_UNDEF_FUNC);
- vbeg = v = nvalloc(op->r.f->nargs + 1);
+ /* The body might be empty, still has to eval the args */
+ nargs = op->r.f->nargs;
+ argvars = nvalloc(nargs);
+ i = 0;
while (op1) {
- var *arg = evaluate(nextarg(&op1), v1);
- copyvar(v, arg);
- v->type |= VF_CHILD;
- v->x.parent = arg;
- if (++v - vbeg >= op->r.f->nargs)
- break;
+ var *arg = evaluate(nextarg(&op1), TMPVAR0);
+ if (i == nargs) {
+ /* call with more arguments than function takes.
+ * (gawk warns: "warning: function 'f' called with more arguments than declared").
+ * They are still evaluated, but discarded: */
+ clrvar(arg);
+ continue;
+ }
+ copyvar(&argvars[i], arg);
+ argvars[i].type |= VF_CHILD;
+ argvars[i].x.parent = arg;
+ i++;
}
- v = fnargs;
- fnargs = vbeg;
+ sv_fnargs = fnargs;
sv_progname = g_progname;
+ fnargs = argvars;
res = evaluate(op->r.f->body.first, res);
+ nvfree(argvars, nargs);
g_progname = sv_progname;
- nvfree(fnargs);
- fnargs = v;
+ fnargs = sv_fnargs;
break;
}
case XC( OC_GETLINE ):
- case XC( OC_PGETLINE ): {
+ debug_printf_eval("GETLINE /\n");
+ case XC( OC_PGETLINE ):
+ debug_printf_eval("PGETLINE\n");
+ {
rstream *rsm;
int i;
if (op1) {
rsm = newfile(L.s);
if (!rsm->F) {
+ /* NB: can't use "opinfo == TI_PGETLINE", would break "cmd" | getline */
if ((opinfo & OPCLSMASK) == OC_PGETLINE) {
rsm->F = popen(L.s, "r");
rsm->is_pipe = TRUE;
@@ -2873,16 +3203,34 @@ static var *evaluate(node *op, var *res)
/* simple builtins */
case XC( OC_FBLTIN ): {
double R_d = R_d; /* for compiler */
+ debug_printf_eval("FBLTIN\n");
+
+ if (op1 && op1->info == TI_COMMA)
+ /* Simple builtins take one arg maximum */
+ syntax_error("Too many arguments");
switch (opn) {
case F_in:
R_d = (long long)L_d;
break;
- case F_rn:
- R_d = (double)rand() / (double)RAND_MAX;
+ case F_rn: /*rand*/
+ if (op1)
+ syntax_error("Too many arguments");
+ {
+#if RAND_MAX >= 0x7fffffff
+ uint32_t u = ((uint32_t)rand() << 16) ^ rand();
+ uint64_t v = ((uint64_t)rand() << 32) | u;
+ /* the above shift+or is optimized out on 32-bit arches */
+# if RAND_MAX > 0x7fffffff
+ v &= 0x7fffffffffffffffULL;
+# endif
+ R_d = (double)v / 0x8000000000000000ULL;
+#else
+# error Not implemented for this value of RAND_MAX
+#endif
break;
-
+ }
case F_co:
if (ENABLE_FEATURE_AWK_LIBM) {
R_d = cos(L_d);
@@ -2922,7 +3270,9 @@ static var *evaluate(node *op, var *res)
srand(seed);
break;
- case F_ti:
+ case F_ti: /*systime*/
+ if (op1)
+ syntax_error("Too many arguments");
R_d = time(NULL);
break;
@@ -2961,7 +3311,7 @@ static var *evaluate(node *op, var *res)
rstream *rsm;
int err = 0;
rsm = (rstream *)hash_search(fdhash, L.s);
- debug_printf_eval("OC_FBLTIN F_cl rsm:%p\n", rsm);
+ debug_printf_eval("OC_FBLTIN close: op1:%p s:'%s' rsm:%p\n", op1, L.s, rsm);
if (rsm) {
debug_printf_eval("OC_FBLTIN F_cl "
"rsm->is_pipe:%d, ->F:%p\n",
@@ -2972,6 +3322,11 @@ static var *evaluate(node *op, var *res)
*/
if (rsm->F)
err = rsm->is_pipe ? pclose(rsm->F) : fclose(rsm->F);
+//TODO: fix this case:
+// $ awk 'BEGIN { print close(""); print ERRNO }'
+// -1
+// close of redirection that was never opened
+// (we print 0, 0)
free(rsm->buffer);
hash_remove(fdhash, L.s);
}
@@ -2986,14 +3341,18 @@ static var *evaluate(node *op, var *res)
}
case XC( OC_BUILTIN ):
+ debug_printf_eval("BUILTIN\n");
res = exec_builtin(op, res);
break;
case XC( OC_SPRINTF ):
+ debug_printf_eval("SPRINTF\n");
setvar_p(res, awk_printf(op1, NULL));
break;
- case XC( OC_UNARY ): {
+ case XC( OC_UNARY ):
+ debug_printf_eval("UNARY\n");
+ {
double Ld, R_d;
Ld = R_d = getvar_i(R.v);
@@ -3023,7 +3382,9 @@ static var *evaluate(node *op, var *res)
break;
}
- case XC( OC_FIELD ): {
+ case XC( OC_FIELD ):
+ debug_printf_eval("FIELD\n");
+ {
int i = (int)getvar_i(R.v);
if (i < 0)
syntax_error(EMSG_NEGATIVE_FIELD);
@@ -3040,26 +3401,33 @@ static var *evaluate(node *op, var *res)
/* concatenation (" ") and index joining (",") */
case XC( OC_CONCAT ):
+ debug_printf_eval("CONCAT /\n");
case XC( OC_COMMA ): {
const char *sep = "";
- if ((opinfo & OPCLSMASK) == OC_COMMA)
+ debug_printf_eval("COMMA\n");
+ if (opinfo == TI_COMMA)
sep = getvar_s(intvar[SUBSEP]);
setvar_p(res, xasprintf("%s%s%s", L.s, sep, R.s));
break;
}
case XC( OC_LAND ):
+ debug_printf_eval("LAND\n");
setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0);
break;
case XC( OC_LOR ):
+ debug_printf_eval("LOR\n");
setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n));
break;
case XC( OC_BINARY ):
- case XC( OC_REPLACE ): {
+ debug_printf_eval("BINARY /\n");
+ case XC( OC_REPLACE ):
+ debug_printf_eval("REPLACE\n");
+ {
double R_d = getvar_i(R.v);
- debug_printf_eval("BINARY/REPLACE: R_d:%f opn:%c\n", R_d, opn);
+ debug_printf_eval("R_d:%f opn:%c\n", R_d, opn);
switch (opn) {
case '+':
L_d += R_d;
@@ -3095,6 +3463,7 @@ static var *evaluate(node *op, var *res)
case XC( OC_COMPARE ): {
int i = i; /* for compiler */
double Ld;
+ debug_printf_eval("COMPARE\n");
if (is_numeric(L.v) && is_numeric(R.v)) {
Ld = getvar_i(L.v) - getvar_i(R.v);
@@ -3121,7 +3490,7 @@ static var *evaluate(node *op, var *res)
default:
syntax_error(EMSG_POSSIBLE_ERROR);
} /* switch */
- next:
+
if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
op = op->a.n;
if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
@@ -3130,7 +3499,10 @@ static var *evaluate(node *op, var *res)
break;
} /* while (op) */
- nvfree(v1);
+ nvfree(tmpvars, 2);
+#undef TMPVAR0
+#undef TMPVAR1
+
debug_printf_eval("returning from %s(): %p\n", __func__, res);
return res;
#undef fnargs
@@ -3138,25 +3510,21 @@ static var *evaluate(node *op, var *res)
#undef sreg
}
-
/* -------- main & co. -------- */
-static int awk_exit(int r)
+static int awk_exit(void)
{
- var tv;
unsigned i;
- hash_item *hi;
-
- zero_out_var(&tv);
if (!exiting) {
exiting = TRUE;
nextrec = FALSE;
- evaluate(endseq.first, &tv);
+ evaluate(endseq.first, &G.exit__tmpvar);
}
/* waiting for children */
for (i = 0; i < fdhash->csize; i++) {
+ hash_item *hi;
hi = fdhash->items[i];
while (hi) {
if (hi->data.rs.F && hi->data.rs.is_pipe)
@@ -3165,65 +3533,7 @@ static int awk_exit(int r)
}
}
- exit(r);
-}
-
-/* if expr looks like "var=value", perform assignment and return 1,
- * otherwise return 0 */
-static int is_assignment(const char *expr)
-{
- char *exprc, *val;
-
- if (!isalnum_(*expr) || (val = strchr(expr, '=')) == NULL) {
- return FALSE;
- }
-
- exprc = xstrdup(expr);
- val = exprc + (val - expr);
- *val++ = '\0';
-
- unescape_string_in_place(val);
- setvar_u(newvar(exprc), val);
- free(exprc);
- return TRUE;
-}
-
-/* switch to next input file */
-static rstream *next_input_file(void)
-{
-#define rsm (G.next_input_file__rsm)
-#define files_happen (G.next_input_file__files_happen)
-
- FILE *F;
- const char *fname, *ind;
-
- if (rsm.F)
- fclose(rsm.F);
- rsm.F = NULL;
- rsm.pos = rsm.adv = 0;
-
- for (;;) {
- if (getvar_i(intvar[ARGIND])+1 >= getvar_i(intvar[ARGC])) {
- if (files_happen)
- return NULL;
- fname = "-";
- F = stdin;
- break;
- }
- ind = getvar_s(incvar(intvar[ARGIND]));
- fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
- if (fname && *fname && !is_assignment(fname)) {
- F = xfopen_stdin(fname);
- break;
- }
- }
-
- files_happen = TRUE;
- setvar_s(intvar[FILENAME], fname);
- rsm.F = F;
- return &rsm;
-#undef rsm
-#undef files_happen
+ exit(G.exitcode);
}
int awk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -3236,12 +3546,7 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
llist_t *list_e = NULL;
#endif
- int i, j;
- var *v;
- var tv;
- char **envp;
- char *vnames = (char *)vNames; /* cheat */
- char *vvalues = (char *)vValues;
+ int i;
INIT_G();
@@ -3250,48 +3555,43 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
if (ENABLE_LOCALE_SUPPORT)
setlocale(LC_NUMERIC, "C");
- zero_out_var(&tv);
-
- /* allocate global buffer */
- g_buf = xmalloc(MAXVARFMT + 1);
-
- vhash = hash_init();
- ahash = hash_init();
- fdhash = hash_init();
- fnhash = hash_init();
-
/* initialize variables */
- for (i = 0; *vnames; i++) {
- intvar[i] = v = newvar(nextword(&vnames));
- if (*vvalues != '\377')
- setvar_s(v, nextword(&vvalues));
- else
- setvar_i(v, 0);
-
- if (*vnames == '*') {
- v->type |= VF_SPECIAL;
- vnames++;
+ vhash = hash_init();
+ {
+ char *vnames = (char *)vNames; /* cheat */
+ char *vvalues = (char *)vValues;
+ for (i = 0; *vnames; i++) {
+ var *v;
+ intvar[i] = v = newvar(nextword(&vnames));
+ if (*vvalues != '\377')
+ setvar_s(v, nextword(&vvalues));
+ else
+ setvar_i(v, 0);
+
+ if (*vnames == '*') {
+ v->type |= VF_SPECIAL;
+ vnames++;
+ }
}
}
handle_special(intvar[FS]);
handle_special(intvar[RS]);
- newfile("/dev/stdin")->F = stdin;
- newfile("/dev/stdout")->F = stdout;
- newfile("/dev/stderr")->F = stderr;
-
/* Huh, people report that sometimes environ is NULL. Oh well. */
- if (environ) for (envp = environ; *envp; envp++) {
- /* environ is writable, thus we don't strdup it needlessly */
- char *s = *envp;
- char *s1 = strchr(s, '=');
- if (s1) {
- *s1 = '\0';
- /* Both findvar and setvar_u take const char*
- * as 2nd arg -> environment is not trashed */
- setvar_u(findvar(iamarray(intvar[ENVIRON]), s), s1 + 1);
- *s1 = '=';
+ if (environ) {
+ char **envp;
+ for (envp = environ; *envp; envp++) {
+ /* environ is writable, thus we don't strdup it needlessly */
+ char *s = *envp;
+ char *s1 = strchr(s, '=');
+ if (s1) {
+ *s1 = '\0';
+ /* Both findvar and setvar_u take const char*
+ * as 2nd arg -> environment is not trashed */
+ setvar_u(findvar(iamarray(intvar[ENVIRON]), s), s1 + 1);
+ *s1 = '=';
+ }
}
}
opt = getopt32(argv, OPTSTR_AWK, &opt_F, &list_v, &list_f, IF_FEATURE_AWK_GNU_EXTENSIONS(&list_e,) NULL);
@@ -3307,20 +3607,19 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
if (!is_assignment(llist_pop(&list_v)))
bb_show_usage();
}
+
+ /* Parse all supplied programs */
+ fnhash = hash_init();
+ ahash = hash_init();
while (list_f) {
- char *s = NULL;
- FILE *from_file;
+ int fd;
+ char *s;
g_progname = llist_pop(&list_f);
- from_file = xfopen_stdin(g_progname);
- /* one byte is reserved for some trick in next_token */
- for (i = j = 1; j > 0; i += j) {
- s = xrealloc(s, i + 4096);
- j = fread(s + i, 1, 4094, from_file);
- }
- s[i] = '\0';
- fclose(from_file);
- parse_program(s + 1);
+ fd = xopen_stdin(g_progname);
+ s = xmalloc_read(fd, NULL); /* it's NUL-terminated */
+ close(fd);
+ parse_program(s);
free(s);
}
g_progname = "cmd. line";
@@ -3329,11 +3628,23 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
parse_program(llist_pop(&list_e));
}
#endif
+//FIXME: preserve order of -e and -f
+//TODO: implement -i LIBRARY and -E FILE too, they are easy-ish
if (!(opt & (OPT_f | OPT_e))) {
if (!*argv)
bb_show_usage();
parse_program(*argv++);
}
+ /* Free unused parse structures */
+ //hash_free(fnhash); // ~250 bytes when empty, used only for function names
+ //^^^^^^^^^^^^^^^^^ does not work, hash_clear() inside SEGVs
+ // (IOW: hash_clear() assumes it's a hash of variables. fnhash is not).
+ free(fnhash->items);
+ free(fnhash);
+ fnhash = NULL; // debug
+ //hash_free(ahash); // empty after parsing, will reuse as fdhash instead of freeing
+
+ /* Parsing done, on to executing */
/* fill in ARGV array */
setari_u(intvar[ARGV], 0, "awk");
@@ -3342,9 +3653,14 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
setari_u(intvar[ARGV], ++i, *argv++);
setvar_i(intvar[ARGC], i + 1);
- evaluate(beginseq.first, &tv);
+ //fdhash = ahash; // done via define
+ newfile("/dev/stdin")->F = stdin;
+ newfile("/dev/stdout")->F = stdout;
+ newfile("/dev/stderr")->F = stderr;
+
+ evaluate(beginseq.first, &G.main__tmpvar);
if (!mainseq.first && !endseq.first)
- awk_exit(EXIT_SUCCESS);
+ awk_exit();
/* input file could already be opened in BEGIN block */
if (!iF)
@@ -3359,7 +3675,7 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
nextrec = FALSE;
incvar(intvar[NR]);
incvar(intvar[FNR]);
- evaluate(mainseq.first, &tv);
+ evaluate(mainseq.first, &G.main__tmpvar);
if (nextfile)
break;
@@ -3371,6 +3687,6 @@ int awk_main(int argc UNUSED_PARAM, char **argv)
iF = next_input_file();
}
- awk_exit(EXIT_SUCCESS);
+ awk_exit();
/*return 0;*/
}
diff --git a/editors/cmp.c b/editors/cmp.c
index 6e27a841a..e106d814e 100644
--- a/editors/cmp.c
+++ b/editors/cmp.c
@@ -18,7 +18,7 @@
//kbuild:lib-$(CONFIG_CMP) += cmp.o
//usage:#define cmp_trivial_usage
-//usage: "[-l] [-s] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]"
+//usage: "[-ls] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]"
//usage:#define cmp_full_usage "\n\n"
//usage: "Compare FILE1 with FILE2 (or stdin)\n"
//usage: "\n -l Write the byte numbers (decimal) and values (octal)"
diff --git a/editors/sed.c b/editors/sed.c
index d3444003e..a6845a979 100644
--- a/editors/sed.c
+++ b/editors/sed.c
@@ -70,7 +70,7 @@
//usage:#define sed_full_usage "\n\n"
//usage: " -e CMD Add CMD to sed commands to be executed"
//usage: "\n -f FILE Add FILE contents to sed commands to be executed"
-//usage: "\n -i[SFX] Edit files in-place (otherwise sends to stdout)"
+//usage: "\n -i[SFX] Edit files in-place (otherwise write to stdout)"
//usage: "\n Optionally back files up, appending SFX"
//usage: "\n -n Suppress automatic printing of pattern space"
//usage: "\n -r,-E Use extended regex syntax"
diff --git a/editors/vi.c b/editors/vi.c
index 4a7f8c3c2..23a44d597 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -181,7 +181,7 @@
//kbuild:lib-$(CONFIG_VI) += vi.o
//usage:#define vi_trivial_usage
-//usage: IF_FEATURE_VI_COLON("[-c CMD] ")IF_FEATURE_VI_READONLY("[-R] ")"[FILE]..."
+//usage: IF_FEATURE_VI_COLON("[-c CMD] ")IF_FEATURE_VI_READONLY("[-R] ")"[-H] [FILE]..."
//usage:#define vi_full_usage "\n\n"
//usage: "Edit FILE\n"
//usage: IF_FEATURE_VI_COLON(
@@ -191,6 +191,7 @@
//usage: "\n -R Read-only"
//usage: )
//usage: "\n -H List available features"
+// note: non-standard, "vim -H" is Hebrew mode (bidi support)
#include "libbb.h"
// Should be after libbb.h: on some systems regex.h needs sys/types.h:
@@ -307,7 +308,6 @@ struct globals {
#define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
#define ignorecase (vi_setops & VI_IGNORECASE)
#define showmatch (vi_setops & VI_SHOWMATCH )
-#define openabove (vi_setops & VI_TABSTOP )
// order of constants and strings must match
#define OPTS_STR \
"ai\0""autoindent\0" \
@@ -316,15 +316,10 @@ struct globals {
"ic\0""ignorecase\0" \
"sm\0""showmatch\0" \
"ts\0""tabstop\0"
-#define set_openabove() (vi_setops |= VI_TABSTOP)
-#define clear_openabove() (vi_setops &= ~VI_TABSTOP)
#else
#define autoindent (0)
#define expandtab (0)
#define err_method (0)
-#define openabove (0)
-#define set_openabove() ((void)0)
-#define clear_openabove() ((void)0)
#endif
#if ENABLE_FEATURE_VI_READONLY
@@ -380,6 +375,10 @@ struct globals {
#if ENABLE_FEATURE_VI_SEARCH
char *last_search_pattern; // last pattern from a '/' or '?' search
#endif
+#if ENABLE_FEATURE_VI_SETOPTS
+ int indentcol; // column of recently autoindent, 0 or -1
+#endif
+ smallint cmd_error;
// former statics
#if ENABLE_FEATURE_VI_YANKMARK
@@ -503,6 +502,8 @@ struct globals {
#define ioq_start (G.ioq_start )
#define dotcnt (G.dotcnt )
#define last_search_pattern (G.last_search_pattern)
+#define indentcol (G.indentcol )
+#define cmd_error (G.cmd_error )
#define edit_file__cur_line (G.edit_file__cur_line)
#define refresh__old_offset (G.refresh__old_offset)
@@ -537,6 +538,7 @@ struct globals {
last_modified_count = -1; \
/* "" but has space for 2 chars: */ \
IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
+ tabstop = 8; \
} while (0)
#if ENABLE_FEATURE_VI_CRASHME
@@ -1099,6 +1101,7 @@ static void indicate_error(void)
if (crashme > 0)
return;
#endif
+ cmd_error = TRUE;
if (!err_method) {
write1(ESC_BELL);
} else {
@@ -2103,15 +2106,26 @@ static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at
return bias;
}
+// find number of characters in indent, p must be at beginning of line
+static size_t indent_len(char *p)
+{
+ char *r = p;
+
+ while (r < (end - 1) && isblank(*r))
+ r++;
+ return r - p;
+}
+
#if !ENABLE_FEATURE_VI_UNDO
#define char_insert(a,b,c) char_insert(a,b)
#endif
static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
{
#if ENABLE_FEATURE_VI_SETOPTS
- char *q;
size_t len;
+ int col, ntab, nspc;
#endif
+ char *bol = begin_line(p);
if (c == 22) { // Is this an ctrl-V?
p += stupid_insert(p, '^'); // use ^ to indicate literal next
@@ -2130,28 +2144,39 @@ static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
cmdcnt = 0;
end_cmd_q(); // stop adding to q
last_status_cksum = 0; // force status update
- if ((p[-1] != '\n') && (dot > text)) {
+ if ((dot > text) && (p[-1] != '\n')) {
p--;
}
- } else if (c == 4) { // ctrl-D reduces indentation
- int prev;
- char *r, *bol;
- bol = begin_line(p);
- for (r = bol; r < end_line(p); ++r) {
- if (!isblank(*r))
- break;
+#if ENABLE_FEATURE_VI_SETOPTS
+ if (autoindent) {
+ len = indent_len(bol);
+ if (len && get_column(bol + len) == indentcol && bol[len] == '\n') {
+ // remove autoindent from otherwise empty line
+ text_hole_delete(bol, bol + len - 1, undo);
+ p = bol;
+ }
}
-
- prev = prev_tabstop(get_column(r));
+#endif
+ } else if (c == 4) { // ctrl-D reduces indentation
+ char *r = bol + indent_len(bol);
+ int prev = prev_tabstop(get_column(r));
while (r > bol && get_column(r) > prev) {
if (p > bol)
p--;
r--;
r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
}
+
+#if ENABLE_FEATURE_VI_SETOPTS
+ if (autoindent && indentcol && r == end_line(p)) {
+ // record changed size of autoindent
+ indentcol = get_column(p);
+ return p;
+ }
+#endif
#if ENABLE_FEATURE_VI_SETOPTS
} else if (c == '\t' && expandtab) { // expand tab
- int col = get_column(p);
+ col = get_column(p);
col = next_tabstop(col) - col + 1;
while (col--) {
# if ENABLE_FEATURE_VI_UNDO
@@ -2186,27 +2211,46 @@ static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
showmatching(p - 1);
}
if (autoindent && c == '\n') { // auto indent the new line
- // use current/previous line as template
- q = openabove ? p : prev_line(p);
- len = strspn(q, " \t"); // space or tab
- if (openabove) {
- p--; // this replaces dot_prev() in do_cmd()
- q += len; // template will be shifted by text_hole_make()
+ // use indent of current/previous line
+ bol = indentcol < 0 ? p : prev_line(p);
+ len = indent_len(bol);
+ col = get_column(bol + len);
+
+ if (len && col == indentcol) {
+ // previous line was empty except for autoindent
+ // move the indent to the current line
+ memmove(bol + 1, bol, len);
+ *bol = '\n';
+ return p;
}
+
+ if (indentcol < 0)
+ p--; // open above, indent before newly inserted NL
+
if (len) {
- uintptr_t bias;
- bias = text_hole_make(p, len);
- p += bias;
- q += bias;
+ indentcol = col;
+ if (expandtab) {
+ ntab = 0;
+ nspc = col;
+ } else {
+ ntab = col / tabstop;
+ nspc = col % tabstop;
+ }
+ p += text_hole_make(p, ntab + nspc);
# if ENABLE_FEATURE_VI_UNDO
- undo_push_insert(p, len, undo);
+ undo_push_insert(p, ntab + nspc, undo);
# endif
- memcpy(p, q, len);
- p += len;
+ memset(p, '\t', ntab);
+ p += ntab;
+ memset(p, ' ', nspc);
+ return p + nspc;
}
}
#endif
}
+#if ENABLE_FEATURE_VI_SETOPTS
+ indentcol = 0;
+#endif
return p;
}
@@ -2332,16 +2376,16 @@ static char *char_search(char *p, const char *pat, int dir_and_range)
struct re_pattern_buffer preg;
const char *err;
char *q;
- int i;
- int size;
- int range;
+ int i, size, range, start;
- re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
+ re_syntax_options = RE_SYNTAX_POSIX_BASIC & (~RE_DOT_NEWLINE);
if (ignorecase)
- re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
+ re_syntax_options |= RE_ICASE;
memset(&preg, 0, sizeof(preg));
err = re_compile_pattern(pat, strlen(pat), &preg);
+ preg.not_bol = p != text;
+ preg.not_eol = p != end - 1;
if (err != NULL) {
status_line_bold("bad search pattern '%s': %s", pat, err);
return p;
@@ -2359,31 +2403,26 @@ static char *char_search(char *p, const char *pat, int dir_and_range)
// RANGE could be negative if we are searching backwards
range = q - p;
- q = p;
- size = range;
if (range < 0) {
- size = -size;
- q = p - size;
- if (q < text)
- q = text;
+ size = -range;
+ start = size;
+ } else {
+ size = range;
+ start = 0;
}
+ q = p - start;
+ if (q < text)
+ q = text;
// search for the compiled pattern, preg, in p[]
- // range < 0: search backward
- // range > 0: search forward
- // 0 < start < size
+ // range < 0, start == size: search backward
+ // range > 0, start == 0: search forward
// re_search() < 0: not found or error
// re_search() >= 0: index of found pattern
// struct pattern char int int int struct reg
// re_search(*pattern_buffer, *string, size, start, range, *regs)
- i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
+ i = re_search(&preg, q, size, start, range, /*struct re_registers*:*/ NULL);
regfree(&preg);
- if (i < 0)
- return NULL;
- if (dir_and_range > 0) // FORWARD?
- p = p + i;
- else
- p = p - i;
- return p;
+ return i < 0 ? NULL : q + i;
}
# else
# if ENABLE_FEATURE_VI_SETOPTS
@@ -2581,7 +2620,6 @@ static void setops(char *args, int flg_no)
index = 1 << (index >> 1); // convert to VI_bit
if (index & VI_TABSTOP) {
- // don't set this bit in vi_setops, it's reused as 'openabove'
int t;
if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
goto bad;
@@ -2639,6 +2677,75 @@ static char *expand_args(char *args)
# endif
#endif /* FEATURE_VI_COLON */
+#if ENABLE_FEATURE_VI_REGEX_SEARCH
+# define MAX_SUBPATTERN 10 // subpatterns \0 .. \9
+
+// Like strchr() but skipping backslash-escaped characters
+static char *strchr_backslash(const char *s, int c)
+{
+ while (*s) {
+ if (*s == c)
+ return (char *)s;
+ if (*s == '\\')
+ if (*++s == '\0')
+ break;
+ s++;
+ }
+ return NULL;
+}
+
+// If the return value is not NULL the caller should free R
+static char *regex_search(char *q, regex_t *preg, const char *Rorig,
+ size_t *len_F, size_t *len_R, char **R)
+{
+ regmatch_t regmatch[MAX_SUBPATTERN], *cur_match;
+ char *found = NULL;
+ const char *t;
+ char *r;
+
+ regmatch[0].rm_so = 0;
+ regmatch[0].rm_eo = end_line(q) - q;
+ if (regexec(preg, q, MAX_SUBPATTERN, regmatch, REG_STARTEND) != 0)
+ return found;
+
+ found = q + regmatch[0].rm_so;
+ *len_F = regmatch[0].rm_eo - regmatch[0].rm_so;
+ *R = NULL;
+
+ fill_result:
+ // first pass calculates len_R, second fills R
+ *len_R = 0;
+ for (t = Rorig, r = *R; *t; t++) {
+ size_t len = 1; // default is to copy one char from replace pattern
+ const char *from = t;
+ if (*t == '\\') {
+ from = ++t; // skip backslash
+ if (*t >= '0' && *t < '0' + MAX_SUBPATTERN) {
+ cur_match = regmatch + (*t - '0');
+ if (cur_match->rm_so >= 0) {
+ len = cur_match->rm_eo - cur_match->rm_so;
+ from = q + cur_match->rm_so;
+ }
+ }
+ }
+ *len_R += len;
+ if (*R) {
+ memcpy(r, from, len);
+ r += len;
+ /* *r = '\0'; - xzalloc did it */
+ }
+ }
+ if (*R == NULL) {
+ *R = xzalloc(*len_R + 1);
+ goto fill_result;
+ }
+
+ return found;
+}
+#else /* !ENABLE_FEATURE_VI_REGEX_SEARCH */
+# define strchr_backslash(s, c) strchr(s, c)
+#endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */
+
// buf must be no longer than MAX_INPUT_LEN!
static void colon(char *buf)
{
@@ -2959,12 +3066,10 @@ static void colon(char *buf)
status_line_bold("No current filename");
goto ret;
}
- if (e < 0) { // no addr given- read after current line
- q = begin_line(dot);
- } else if (e == 0) { // user said ":0r foo"
+ if (e == 0) { // user said ":0r foo"
q = text;
- } else { // addr given- read after that line
- q = next_line(find_line(e));
+ } else { // read after given line or current line if none given
+ q = next_line(e > 0 ? find_line(e) : dot);
// read after last line
if (q == end-1)
++q;
@@ -3048,23 +3153,42 @@ static void colon(char *buf)
# if ENABLE_FEATURE_VI_VERBOSE_STATUS
int last_line = 0, lines = 0;
# endif
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+ regex_t preg;
+ int cflags;
+ char *Rorig;
+# if ENABLE_FEATURE_VI_UNDO
+ int undo = 0;
+# endif
+# endif
// F points to the "find" pattern
// R points to the "replace" pattern
// replace the cmd line delimiters "/" with NULs
c = buf[1]; // what is the delimiter
F = buf + 2; // start of "find"
- R = strchr(F, c); // middle delimiter
+ R = strchr_backslash(F, c); // middle delimiter
if (!R)
goto colon_s_fail;
len_F = R - F;
*R++ = '\0'; // terminate "find"
- flags = strchr(R, c);
+ flags = strchr_backslash(R, c);
if (flags) {
*flags++ = '\0'; // terminate "replace"
gflag = *flags;
}
- len_R = strlen(R);
+
+ if (len_F) { // save "find" as last search pattern
+ free(last_search_pattern);
+ last_search_pattern = xstrdup(F - 1);
+ last_search_pattern[0] = '/';
+ } else if (last_search_pattern[1] == '\0') {
+ status_line_bold("No previous search");
+ goto ret;
+ } else {
+ F = last_search_pattern + 1;
+ len_F = strlen(F);
+ }
if (e < 0) { // no addr given
q = begin_line(dot); // start with cur line
@@ -3074,31 +3198,67 @@ static void colon(char *buf)
b = e;
}
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+ Rorig = R;
+ cflags = 0;
+ if (ignorecase)
+ cflags = REG_ICASE;
+ memset(&preg, 0, sizeof(preg));
+ if (regcomp(&preg, F, cflags) != 0) {
+ status_line(":s bad search pattern");
+ goto regex_search_end;
+ }
+# else
+ len_R = strlen(R);
+# endif
+
for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
char *ls = q; // orig line start
char *found;
vc4:
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+ found = regex_search(q, &preg, Rorig, &len_F, &len_R, &R);
+# else
found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
+# endif
if (found) {
uintptr_t bias;
// we found the "find" pattern - delete it
// For undo support, the first item should not be chained
- text_hole_delete(found, found + len_F - 1,
- subs ? ALLOW_UNDO_CHAIN: ALLOW_UNDO);
- // can't do this above, no undo => no third argument
- subs++;
-# if ENABLE_FEATURE_VI_VERBOSE_STATUS
- if (last_line != i) {
- last_line = i;
- ++lines;
+ // This needs to be handled differently depending on
+ // whether or not regex support is enabled.
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+# define TEST_LEN_F len_F // len_F may be zero
+# define TEST_UNDO1 undo++
+# define TEST_UNDO2 undo++
+# else
+# define TEST_LEN_F 1 // len_F is never zero
+# define TEST_UNDO1 subs
+# define TEST_UNDO2 1
+# endif
+ if (TEST_LEN_F) // match can be empty, no delete needed
+ text_hole_delete(found, found + len_F - 1,
+ TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
+ if (len_R != 0) { // insert the "replace" pattern, if required
+ bias = string_insert(found, R,
+ TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
+ found += bias;
+ ls += bias;
+ //q += bias; - recalculated anyway
}
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+ free(R);
# endif
- // insert the "replace" patern
- bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
- found += bias;
- ls += bias;
- dot = ls;
- //q += bias; - recalculated anyway
+ if (TEST_LEN_F || len_R != 0) {
+ dot = ls;
+ subs++;
+# if ENABLE_FEATURE_VI_VERBOSE_STATUS
+ if (last_line != i) {
+ last_line = i;
+ ++lines;
+ }
+# endif
+ }
// check for "global" :s/foo/bar/g
if (gflag == 'g') {
if ((found + len_R) < end_line(ls)) {
@@ -3118,6 +3278,10 @@ static void colon(char *buf)
status_line("%d substitutions on %d lines", subs, lines);
# endif
}
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+ regex_search_end:
+ regfree(&preg);
+# endif
# endif /* FEATURE_VI_SEARCH */
} else if (strncmp(cmd, "version", i) == 0) { // show software version
status_line(BB_VER);
@@ -3361,8 +3525,11 @@ static int find_range(char **start, char **stop, int cmd)
#endif
// these cmds operate on whole lines
buftype = WHOLE;
- if (--cmdcnt > 0)
+ if (--cmdcnt > 0) {
do_cmd('j');
+ if (cmd_error)
+ buftype = -1;
+ }
} else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
// Most operate on char positions within a line. Of those that
// don't '%' needs no special treatment, search commands are
@@ -3388,10 +3555,12 @@ static int find_range(char **start, char **stop, int cmd)
// for non-change operations WS after NL is not part of word
if (cmd != 'c' && dot != t && *dot != '\n')
dot = t;
- } else if (strchr("GHL+-jk'\r\n", c)) {
+ } else if (strchr("GHL+-gjk'\r\n", c)) {
// these operate on whole lines
buftype = WHOLE;
do_cmd(c); // execute movement cmd
+ if (cmd_error)
+ buftype = -1;
} else if (c == ' ' || c == 'l') {
// forward motion by character
int tmpcnt = (cmdcnt ?: 1);
@@ -3477,6 +3646,7 @@ static void do_cmd(int c)
// p = q = save_dot = buf; // quiet the compiler
memset(buf, '\0', sizeof(buf));
keep_index = FALSE;
+ cmd_error = FALSE;
show_status_line();
@@ -3597,24 +3767,30 @@ static void do_cmd(int c)
case 10: // Newline ^J
case 'j': // j- goto next line, same col
case KEYCODE_DOWN: // cursor key Down
+ case 13: // Carriage Return ^M
+ case '+': // +- goto next line
+ q = dot;
do {
- dot_next(); // go to next B-o-l
+ p = next_line(q);
+ if (p == end_line(q)) {
+ indicate_error();
+ goto dc1;
+ }
+ q = p;
} while (--cmdcnt > 0);
- // try to stay in saved column
- dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
- keep_index = TRUE;
+ dot = q;
+ if (c == 13 || c == '+') {
+ dot_skip_over_ws();
+ } else {
+ // try to stay in saved column
+ dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
+ keep_index = TRUE;
+ }
break;
case 12: // ctrl-L force redraw whole screen
case 18: // ctrl-R force redraw
redraw(TRUE); // this will redraw the entire display
break;
- case 13: // Carriage Return ^M
- case '+': // +- goto next line
- do {
- dot_next();
- } while (--cmdcnt > 0);
- dot_skip_over_ws();
- break;
case 21: // ctrl-U scroll up half screen
dot_scroll((rows - 2) / 2, -1);
break;
@@ -3655,6 +3831,8 @@ static void do_cmd(int c)
dot = q;
dot_begin(); // go to B-o-l
dot_skip_over_ws();
+ } else {
+ indicate_error();
}
} else if (c1 == '\'') { // goto previous context
dot = swap_context(dot); // swap current and previous context
@@ -3717,6 +3895,7 @@ static void do_cmd(int c)
# endif
} while (--cmdcnt > 0);
dot += cnt;
+ dot_skip_over_ws();
# if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
yank_status("Put", p, i);
# endif
@@ -3779,12 +3958,6 @@ static void do_cmd(int c)
case ',': // ,- repeat latest search in opposite direction
dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
break;
- case '-': // -- goto prev line
- do {
- dot_prev();
- } while (--cmdcnt > 0);
- dot_skip_over_ws();
- break;
#if ENABLE_FEATURE_VI_DOT_CMD
case '.': // .- repeat the last modifying command
// Stuff the last_modifying_cmd back into stdin
@@ -3972,6 +4145,7 @@ static void do_cmd(int c)
buf[1] = (c1 >= 0 ? c1 : '*');
buf[2] = '\0';
not_implemented(buf);
+ cmd_error = TRUE;
break;
}
if (cmdcnt == 0)
@@ -3990,9 +4164,10 @@ static void do_cmd(int c)
if (cmdcnt > (rows - 1)) {
cmdcnt = (rows - 1);
}
- if (--cmdcnt > 0) {
- do_cmd('+');
+ while (--cmdcnt > 0) {
+ dot_next();
}
+ dot_begin();
dot_skip_over_ws();
break;
case 'I': // I- insert before first non-blank
@@ -4029,8 +4204,8 @@ static void do_cmd(int c)
if (cmdcnt > (rows - 1)) {
cmdcnt = (rows - 1);
}
- if (--cmdcnt > 0) {
- do_cmd('-');
+ while (--cmdcnt > 0) {
+ dot_prev();
}
dot_begin();
dot_skip_over_ws();
@@ -4043,17 +4218,18 @@ static void do_cmd(int c)
break;
case 'O': // O- open an empty line above
dot_begin();
- set_openabove();
+#if ENABLE_FEATURE_VI_SETOPTS
+ indentcol = -1;
+#endif
goto dc3;
case 'o': // o- open an empty line below
dot_end();
dc3:
dot = char_insert(dot, '\n', ALLOW_UNDO);
if (c == 'O' && !autoindent) {
- // done in char_insert() for openabove+autoindent
+ // done in char_insert() for 'O'+autoindent
dot_prev();
}
- clear_openabove();
goto dc_i;
break;
case 'R': // R- continuous Replace char
@@ -4172,6 +4348,9 @@ static void do_cmd(int c)
if (dot != (end-1)) {
dot_prev();
}
+ } else if (c == 'd') {
+ dot_begin();
+ dot_skip_over_ws();
} else {
dot = save_dot;
}
@@ -4191,12 +4370,24 @@ static void do_cmd(int c)
}
case 'k': // k- goto prev line, same col
case KEYCODE_UP: // cursor key Up
+ case '-': // -- goto prev line
+ q = dot;
do {
- dot_prev();
+ p = prev_line(q);
+ if (p == begin_line(q)) {
+ indicate_error();
+ goto dc1;
+ }
+ q = p;
} while (--cmdcnt > 0);
- // try to stay in saved column
- dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
- keep_index = TRUE;
+ dot = q;
+ if (c == '-') {
+ dot_skip_over_ws();
+ } else {
+ // try to stay in saved column
+ dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
+ keep_index = TRUE;
+ }
break;
case 'r': // r- replace the current char with user input
c1 = get_one_char(); // get the replacement char
@@ -4577,7 +4768,6 @@ static void edit_file(char *fn)
cmd_mode = 0; // 0=command 1=insert 2='R'eplace
cmdcnt = 0;
- tabstop = 8;
offset = 0; // no horizontal offset
c = '\0';
#if ENABLE_FEATURE_VI_DOT_CMD
@@ -4705,7 +4895,11 @@ int vi_main(int argc, char **argv)
initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
}
#endif
- while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
+ while ((c = getopt(argc, argv,
+#if ENABLE_FEATURE_VI_CRASHME
+ "C"
+#endif
+ "RHh" IF_FEATURE_VI_COLON("c:"))) != -1) {
switch (c) {
#if ENABLE_FEATURE_VI_CRASHME
case 'C':
diff --git a/examples/udhcp/udhcpd.conf b/examples/udhcp/udhcpd.conf
index df1258aaf..6eb10852e 100644
--- a/examples/udhcp/udhcpd.conf
+++ b/examples/udhcp/udhcpd.conf
@@ -79,7 +79,7 @@ option staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1
option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4"
option 14 "dumpfile"
-# Currently supported options [hex option value] (for more info, see options.c):
+# Currently supported options [hex option value] (for more info, see common.c):
#opt lease NUM # [0x33]
#opt subnet IP # [0x01]
#opt broadcast IP # [0x1c]
@@ -91,17 +91,20 @@ option 14 "dumpfile"
#opt search STRING_LIST # [0x77] search domains
#opt nisdomain STRING # [0x28]
#opt timezone NUM # [0x02] (localtime - UTC_time) in seconds. signed
+#opt tzstr STRING # [0x64] RFC 4833. IEEE 1003.1 TZ string
+#opt tzdbstr STRING # [0x65] RFC 4833. Reference to the TZ database string
#opt tftp STRING # [0x42] tftp server name
#opt bootfile STRING # [0x43] tftp file to download (e.g. kernel image)
#opt bootsize NUM # [0x0d] size of that file
#opt rootpath STRING # [0x11] (NFS) path to mount as root fs
#opt wpad STRING # [0xfc] Web Proxy Auto Discovery Protocol
#opt serverid IP # [0x36] default: server's IP
-#opt message STRING # [0x38] error message (udhcpd sends it on success too)
+#opt message STRING # [0x38] error message (if set, udhcpd would send it on success too)
#opt vlanid NUM # [0x84] 802.1P VLAN ID
#opt vlanpriority NUM # [0x85] 802.1Q VLAN priority
+#opt vendor STRING # [0x3c] client's vendor string, not intended to be sent by DHCP servers
# RFC 5071: PXELINUX Options
-#opt 0xd0 F100747E # [0xd0] magic
+#opt 0xd0 F100747E # [0xd0] magic needed for other options to be recognized by clients
#opt pxeconffile STRING # [0xd1]
#opt pxepathprefix STRING # [0xd2]
#opt reboottime NUM # [0xd3] bootstrap timeout
diff --git a/findutils/grep.c b/findutils/grep.c
index 10cca83e7..be4362ed0 100644
--- a/findutils/grep.c
+++ b/findutils/grep.c
@@ -57,14 +57,12 @@
#include "common_bufsiz.h"
#include "xregex.h"
-
-/* options */
//usage:#define grep_trivial_usage
//usage: "[-HhnlLoqvsrRiwFE"
//usage: IF_EXTRA_COMPAT("z")
//usage: "] [-m N] "
-//usage: IF_FEATURE_GREP_CONTEXT("[-A/B/C N] ")
-//usage: "PATTERN/-e PATTERN.../-f FILE [FILE]..."
+//usage: IF_FEATURE_GREP_CONTEXT("[-A|B|C N] ")
+//usage: "{ PATTERN | -e PATTERN... | -f FILE... } [FILE]..."
//usage:#define grep_full_usage "\n\n"
//usage: "Search for PATTERN in FILEs (or stdin)\n"
//usage: "\n -H Add 'filename:' prefix"
diff --git a/include/bb_e2fs_defs.h b/include/bb_e2fs_defs.h
index 3f5e3c45b..608cbf0b0 100644
--- a/include/bb_e2fs_defs.h
+++ b/include/bb_e2fs_defs.h
@@ -182,11 +182,12 @@ struct ext2_dx_countlimit {
#define EXT2_NOTAIL_FL 0x00008000 /* file tail should not be merged */
#define EXT2_DIRSYNC_FL 0x00010000 /* Synchronous directory modifications */
#define EXT2_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
-#define EXT3_EXTENTS_FL 0x00080000 /* Inode uses extents */
-#define EXT2_RESERVED_FL 0x80000000 /* reserved for ext2 lib */
-
-#define EXT2_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */
-#define EXT2_FL_USER_MODIFIABLE 0x000080FF /* User modifiable flags */
+#define EXT2_EXTENT_FL 0x00080000 /* Extents */
+#define EXT2_VERITY_FL 0x00100000
+#define EXT2_NOCOW_FL 0x00800000 /* Do not cow file */
+#define EXT2_INLINE_DATA_FL 0x10000000
+#define EXT2_PROJINHERIT_FL 0x20000000
+#define EXT2_CASEFOLD_FL 0x40000000
/*
* ioctl commands
@@ -195,6 +196,18 @@ struct ext2_dx_countlimit {
#define EXT2_IOC_SETFLAGS _IOW('f', 2, long)
#define EXT2_IOC_GETVERSION _IOR('v', 1, long)
#define EXT2_IOC_SETVERSION _IOW('v', 2, long)
+//NB: despite "long" in defs above, these ioctls use an _int_!
+//passing them a pointer to long will read/write only int-sized data!
+struct ext2_fsxattr {
+ uint32_t fsx_xflags; /* xflags field value (get/set) */
+ uint32_t fsx_extsize; /* extsize field value (get/set)*/
+ uint32_t fsx_nextents; /* nextents field value (get) */
+ uint32_t fsx_projid; /* project identifier (get/set) */
+ uint32_t fsx_cowextsize; /* CoW extsize field value (get/set)*/
+ unsigned char fsx_pad[8];
+};
+#define EXT2_IOC_FSGETXATTR _IOR('X', 31, struct ext2_fsxattr)
+#define EXT2_IOC_FSSETXATTR _IOW('X', 32, struct ext2_fsxattr)
/*
* Structure of an inode on the disk
diff --git a/include/dump.h b/include/dump.h
index 9193a6925..10fc5d900 100644
--- a/include/dump.h
+++ b/include/dump.h
@@ -32,8 +32,10 @@ typedef struct dumper_t {
off_t dump_skip; /* bytes to skip */
int dump_length; /* max bytes to read */
smallint dump_vflag; /*enum dump_vflag_t*/
- const char *eofstring;
FS *fshead;
+ const char *xxd_eofstring;
+ off_t address; /* address/offset in stream */
+ long long xxd_displayoff;
} dumper_t;
dumper_t* alloc_dumper(void) FAST_FUNC;
diff --git a/include/libbb.h b/include/libbb.h
index 03f9c35f3..7d6ab4a93 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -450,23 +450,29 @@ enum { /* cp.c, mv.c, install.c depend on these values. CAREFUL when changing th
FILEUTILS_RECUR = 1 << 2, /* -R */
FILEUTILS_FORCE = 1 << 3, /* -f */
FILEUTILS_INTERACTIVE = 1 << 4, /* -i */
- FILEUTILS_MAKE_HARDLINK = 1 << 5, /* -l */
- FILEUTILS_MAKE_SOFTLINK = 1 << 6, /* -s */
- FILEUTILS_DEREF_SOFTLINK = 1 << 7, /* -L */
- FILEUTILS_DEREFERENCE_L0 = 1 << 8, /* -H */
+ FILEUTILS_NO_OVERWRITE = 1 << 5, /* -n */
+ FILEUTILS_MAKE_HARDLINK = 1 << 6, /* -l */
+ FILEUTILS_MAKE_SOFTLINK = 1 << 7, /* -s */
+ FILEUTILS_DEREF_SOFTLINK = 1 << 8, /* -L */
+ FILEUTILS_DEREFERENCE_L0 = 1 << 9, /* -H */
/* -a = -pdR (mapped in cp.c) */
/* -r = -dR (mapped in cp.c) */
/* -P = -d (mapped in cp.c) */
- FILEUTILS_VERBOSE = (1 << 12) * ENABLE_FEATURE_VERBOSE, /* -v */
- FILEUTILS_UPDATE = 1 << 13, /* -u */
- FILEUTILS_NO_TARGET_DIR = 1 << 14, /* -T */
+ FILEUTILS_VERBOSE = (1 << 13) * ENABLE_FEATURE_VERBOSE, /* -v */
+ FILEUTILS_UPDATE = 1 << 14, /* -u */
+ FILEUTILS_NO_TARGET_DIR = 1 << 15, /* -T */
+ FILEUTILS_TARGET_DIR = 1 << 16, /* -t DIR */
#if ENABLE_SELINUX
- FILEUTILS_PRESERVE_SECURITY_CONTEXT = 1 << 15, /* -c */
+ FILEUTILS_PRESERVE_SECURITY_CONTEXT = 1 << 17, /* -c */
#endif
- FILEUTILS_RMDEST = 1 << (16 - !ENABLE_SELINUX), /* --remove-destination */
- /* bit 17 skipped for "cp --parents" */
- FILEUTILS_REFLINK = 1 << (18 - !ENABLE_SELINUX), /* cp --reflink=auto */
- FILEUTILS_REFLINK_ALWAYS = 1 << (19 - !ENABLE_SELINUX), /* cp --reflink[=always] */
+#define FILEUTILS_CP_OPTSTR "pdRfinlsLHarPvuTt:" IF_SELINUX("c")
+/* How many bits in FILEUTILS_CP_OPTSTR? */
+ FILEUTILS_CP_OPTBITS = 18 - !ENABLE_SELINUX,
+
+ FILEUTILS_RMDEST = 1 << (19 - !ENABLE_SELINUX), /* cp --remove-destination */
+ /* bit 18 skipped for "cp --parents" */
+ FILEUTILS_REFLINK = 1 << (20 - !ENABLE_SELINUX), /* cp --reflink=auto */
+ FILEUTILS_REFLINK_ALWAYS = 1 << (21 - !ENABLE_SELINUX), /* cp --reflink[=always] */
/*
* Hole. cp may have some bits set here,
* they should not affect remove_file()/copy_file()
@@ -476,7 +482,7 @@ enum { /* cp.c, mv.c, install.c depend on these values. CAREFUL when changing th
#endif
FILEUTILS_IGNORE_CHMOD_ERR = 1 << 31,
};
-#define FILEUTILS_CP_OPTSTR "pdRfilsLHarPvuT" IF_SELINUX("c")
+
extern int remove_file(const char *path, int flags) FAST_FUNC;
/* NB: without FILEUTILS_RECUR in flags, it will basically "cat"
* the source, not copy (unless "source" is a directory).
@@ -506,6 +512,11 @@ int recursive_action(const char *fileName, unsigned flags,
void *userData
) FAST_FUNC;
+/* Simpler version: call a function on each dirent in a directory */
+int iterate_on_dir(const char *dir_name,
+ int FAST_FUNC (*func)(const char *, struct dirent *, void *),
+ void *private) FAST_FUNC;
+
extern int device_open(const char *device, int mode) FAST_FUNC;
enum { GETPTY_BUFSIZE = 16 }; /* more than enough for "/dev/ttyXXX" */
extern int xgetpty(char *line) FAST_FUNC;
@@ -593,7 +604,7 @@ void bb_signals(int sigs, void (*f)(int)) FAST_FUNC;
/* Unlike signal() and bb_signals, sets handler with sigaction()
* and in a way that while signal handler is run, no other signals
* will be blocked; syscalls will not be restarted: */
-void bb_signals_recursive_norestart(int sigs, void (*f)(int)) FAST_FUNC;
+void bb_signals_norestart(int sigs, void (*f)(int)) FAST_FUNC;
/* syscalls like read() will be interrupted with EINTR: */
void signal_no_SA_RESTART_empty_mask(int sig, void (*handler)(int)) FAST_FUNC;
/* syscalls like read() won't be interrupted (though select/poll will be): */
@@ -1065,10 +1076,10 @@ char *smart_ulltoa5(unsigned long long ul, char buf[5], const char *scale) FAST_
/* If block_size == 0, display size without fractional part,
* else display (size * block_size) with one decimal digit.
* If display_unit == 0, show value no bigger than 1024 with suffix (K,M,G...),
- * else divide by display_unit and do not use suffix. */
+ * else divide by display_unit and do not use suffix.
+ * Returns "auto pointer" */
#define HUMAN_READABLE_MAX_WIDTH 7 /* "1024.0G" */
#define HUMAN_READABLE_MAX_WIDTH_STR "7"
-//TODO: provide pointer to buf (avoid statics)?
const char *make_human_readable_str(unsigned long long size,
unsigned long block_size, unsigned long display_unit) FAST_FUNC;
/* Put a string of hex bytes ("1b2e66fe"...), return advanced pointer */
@@ -1773,6 +1784,7 @@ extern void print_login_issue(const char *issue_file, const char *tty) FAST_FUNC
extern void print_login_prompt(void) FAST_FUNC;
char *xmalloc_ttyname(int fd) FAST_FUNC RETURNS_MALLOC;
+int is_TERM_dumb(void) FAST_FUNC;
/* NB: typically you want to pass fd 0, not 1. Think 'applet | grep something' */
int get_terminal_width_height(int fd, unsigned *width, unsigned *height) FAST_FUNC;
int get_terminal_width(int fd) FAST_FUNC;
diff --git a/include/platform.h b/include/platform.h
index 4633b2507..9e1fb047d 100644
--- a/include/platform.h
+++ b/include/platform.h
@@ -426,6 +426,8 @@ typedef unsigned smalluint;
#define HAVE_SYS_STATFS_H 1
#define HAVE_PRINTF_PERCENTM 1
#define HAVE_WAIT3 1
+#define HAVE_DEV_FD 1
+#define DEV_FD_PREFIX "/dev/fd/"
#if defined(__UCLIBC__)
# if UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32)
diff --git a/libbb/copy_file.c b/libbb/copy_file.c
index 49d1ec9c6..044bc3c20 100644
--- a/libbb/copy_file.c
+++ b/libbb/copy_file.c
@@ -111,6 +111,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
bb_error_msg("'%s' and '%s' are the same file", source, dest);
return -1;
}
+ if (flags & FILEUTILS_NO_OVERWRITE) /* cp -n */
+ return 0;
dest_exists = 1;
}
diff --git a/libbb/dump.c b/libbb/dump.c
index fb7849e7d..f8bb6fd03 100644
--- a/libbb/dump.c
+++ b/libbb/dump.c
@@ -34,7 +34,6 @@ typedef struct priv_dumper_t {
FU *endfu;
off_t savaddress; /* saved address/offset in stream */
off_t eaddress; /* end address */
- off_t address; /* address/offset in stream */
int blocksize;
smallint exitval; /* final exit value */
@@ -211,7 +210,7 @@ static NOINLINE void rewrite(priv_dumper_t *dumper, FS *fs)
pr->bcnt = fu->bcnt;
if (fu->bcnt == 0) {
if (!prec)
- bb_simple_error_msg_and_die("%%s needs precision or byte count");
+ bb_simple_error_msg_and_die("%s needs precision or byte count");
pr->bcnt = atoi(prec);
}
} else
@@ -228,7 +227,8 @@ static NOINLINE void rewrite(priv_dumper_t *dumper, FS *fs)
if ((p1[2] != 'd') && (p1[2] != 'o') && (p1[2] != 'x')) {
goto DO_BAD_CONV_CHAR;
}
- *p1 = p1[2];
+ *p1++ = 'l';
+ *p1++ = 'l';
break;
case 'c': /* %_c: chars, \ooo, \n \r \t etc */
pr->flags = F_C;
@@ -334,14 +334,14 @@ static void do_skip(priv_dumper_t *dumper, const char *fname)
) {
/* If st_size is valid and pub.dump_skip >= st_size */
dumper->pub.dump_skip -= sbuf.st_size;
- dumper->address += sbuf.st_size;
+ dumper->pub.address += sbuf.st_size;
return;
}
if (fseeko(stdin, dumper->pub.dump_skip, SEEK_SET)) {
bb_simple_perror_msg_and_die(fname);
}
- dumper->address += dumper->pub.dump_skip;
- dumper->savaddress = dumper->address;
+ dumper->pub.address += dumper->pub.dump_skip;
+ dumper->savaddress = dumper->pub.address;
dumper->pub.dump_skip = 0;
}
@@ -356,6 +356,7 @@ static NOINLINE int next(priv_dumper_t *dumper)
if (!freopen(fname, "r", stdin)) {
bb_simple_perror_msg(fname);
dumper->exitval = 1;
+ dumper->next__done = 1;
continue;
}
}
@@ -379,7 +380,7 @@ static unsigned char *get(priv_dumper_t *dumper)
int blocksize = dumper->blocksize;
if (!dumper->get__curp) {
- dumper->address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/
+ dumper->pub.address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/
dumper->get__curp = xmalloc(blocksize);
dumper->get__savp = xzalloc(blocksize); /* need to be initialized */
} else {
@@ -387,7 +388,7 @@ static unsigned char *get(priv_dumper_t *dumper)
dumper->get__curp = dumper->get__savp;
dumper->get__savp = tmp;
dumper->savaddress += blocksize;
- dumper->address = dumper->savaddress;
+ dumper->pub.address = dumper->savaddress;
}
need = blocksize;
nread = 0;
@@ -410,7 +411,7 @@ static unsigned char *get(priv_dumper_t *dumper)
}
}
memset(dumper->get__curp + nread, 0, need);
- dumper->eaddress = dumper->address + nread;
+ dumper->eaddress = dumper->pub.address + nread;
return dumper->get__curp;
}
n = fread(dumper->get__curp + nread, sizeof(unsigned char),
@@ -442,7 +443,7 @@ static unsigned char *get(priv_dumper_t *dumper)
}
dumper->pub.dump_vflag = DUP;
dumper->savaddress += blocksize;
- dumper->address = dumper->savaddress;
+ dumper->pub.address = dumper->savaddress;
need = blocksize;
nread = 0;
} else {
@@ -543,8 +544,8 @@ static void display(priv_dumper_t* dumper)
fs = dumper->pub.fshead;
savebp = bp;
- saveaddress = dumper->address;
- for (; fs; fs = fs->nextfs, bp = savebp, dumper->address = saveaddress) {
+ saveaddress = dumper->pub.address;
+ for (; fs; fs = fs->nextfs, bp = savebp, dumper->pub.address = saveaddress) {
FU *fu;
for (fu = fs->nextfu; fu; fu = fu->nextfu) {
int cnt;
@@ -553,14 +554,14 @@ static void display(priv_dumper_t* dumper)
}
for (cnt = fu->reps; cnt; --cnt) {
PR *pr;
- for (pr = fu->nextpr; pr; dumper->address += pr->bcnt,
+ for (pr = fu->nextpr; pr; dumper->pub.address += pr->bcnt,
bp += pr->bcnt, pr = pr->nextpr) {
if (dumper->eaddress
- && dumper->address >= dumper->eaddress
+ && dumper->pub.address >= dumper->eaddress
) {
- if (dumper->pub.eofstring) {
+ if (dumper->pub.xxd_eofstring) {
/* xxd support: requested to not pad incomplete blocks */
- fputs_stdout(dumper->pub.eofstring);
+ fputs_stdout(dumper->pub.xxd_eofstring);
return;
}
if (!(pr->flags & (F_TEXT | F_BPAD)))
@@ -572,7 +573,7 @@ static void display(priv_dumper_t* dumper)
}
switch (pr->flags) {
case F_ADDRESS:
- printf(pr->fmt, (unsigned) dumper->address);
+ printf(pr->fmt, (unsigned long long) dumper->pub.address + dumper->pub.xxd_displayoff);
break;
case F_BPAD:
printf(pr->fmt, "");
@@ -666,15 +667,15 @@ static void display(priv_dumper_t* dumper)
* of blocksize, and no partial block ever found.
*/
if (!dumper->eaddress) {
- if (!dumper->address) {
+ if (!dumper->pub.address) {
return;
}
- dumper->eaddress = dumper->address;
+ dumper->eaddress = dumper->pub.address;
}
for (pr = dumper->endfu->nextpr; pr; pr = pr->nextpr) {
switch (pr->flags) {
case F_ADDRESS:
- printf(pr->fmt, (unsigned) dumper->eaddress);
+ printf(pr->fmt, (unsigned long long) dumper->eaddress + dumper->pub.xxd_displayoff);
break;
case F_TEXT:
printf(pr->fmt);
diff --git a/libbb/getopt32.c b/libbb/getopt32.c
index 378510063..5ab4d66f1 100644
--- a/libbb/getopt32.c
+++ b/libbb/getopt32.c
@@ -89,6 +89,12 @@ getopt32(char **argv, const char *applet_opts, ...)
root:x:0:0:root:/root:/bin/bash
user:x:500:500::/home/user:/bin/bash
+ "^" options string is "^optchars""\0""opt_complementary".
+
+ "!" If the first character in the applet_opts string is a '!',
+ report bad options, missing required options,
+ inconsistent options with all-ones return value (instead of abort.
+
"+" If the first character in the applet_opts string is a plus,
then option processing will stop as soon as a non-option is
encountered in the argv array. Useful for applets like env
@@ -96,10 +102,7 @@ getopt32(char **argv, const char *applet_opts, ...)
env -i ls -d /
Here we want env to process just the '-i', not the '-d'.
- "!" Report bad options, missing required options,
- inconsistent options with all-ones return value (instead of abort).
-
- "^" options string is "^optchars""\0""opt_complementary".
+ (The order of multiple prefixes must be "^!+...")
uint32_t
getopt32long(char **argv, const char *applet_opts, const char *logopts...)
diff --git a/libbb/iterate_on_dir.c b/libbb/iterate_on_dir.c
new file mode 100644
index 000000000..deef72ebf
--- /dev/null
+++ b/libbb/iterate_on_dir.c
@@ -0,0 +1,28 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * See README for additional information
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//kbuild:lib-y += iterate_on_dir.o
+
+#include "libbb.h"
+
+/* Iterate a function on each entry of a directory */
+int FAST_FUNC iterate_on_dir(const char *dir_name,
+ int FAST_FUNC (*func)(const char *, struct dirent *, void *),
+ void *private)
+{
+ DIR *dir;
+ struct dirent *de;
+
+ dir = opendir(dir_name);
+ if (dir == NULL) {
+ return -1;
+ }
+ while ((de = readdir(dir)) != NULL) {
+ func(dir_name, de, private);
+ }
+ closedir(dir);
+ return 0;
+}
diff --git a/libbb/remove_file.c b/libbb/remove_file.c
index cea5d47e6..1505e6218 100644
--- a/libbb/remove_file.c
+++ b/libbb/remove_file.c
@@ -60,11 +60,7 @@ int FAST_FUNC remove_file(const char *path, int flags)
status = -1;
free(new_path);
}
-
- if (closedir(dp) < 0) {
- bb_perror_msg("can't close '%s'", path);
- return -1;
- }
+ closedir(dp);
if (flags & FILEUTILS_INTERACTIVE) {
fprintf(stderr, "%s: remove directory '%s'? ",
diff --git a/libbb/signals.c b/libbb/signals.c
index d3d84ef6a..0bebc847d 100644
--- a/libbb/signals.c
+++ b/libbb/signals.c
@@ -56,7 +56,7 @@ void FAST_FUNC bb_signals(int sigs, void (*f)(int))
}
}
-void FAST_FUNC bb_signals_recursive_norestart(int sigs, void (*f)(int))
+void FAST_FUNC bb_signals_norestart(int sigs, void (*f)(int))
{
int sig_no = 0;
int bit = 1;
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c
index d93d8aaf5..c40dcb706 100644
--- a/libbb/xfuncs.c
+++ b/libbb/xfuncs.c
@@ -303,6 +303,12 @@ int FAST_FUNC get_terminal_width(int fd)
return width;
}
+int FAST_FUNC is_TERM_dumb(void)
+{
+ char *term = getenv("TERM");
+ return term && strcmp(term, "dumb") == 0;
+}
+
int FAST_FUNC tcsetattr_stdin_TCSANOW(const struct termios *tp)
{
return tcsetattr(STDIN_FILENO, TCSANOW, tp);
diff --git a/loginutils/login.c b/loginutils/login.c
index 66ac7cf4c..ce87e318a 100644
--- a/loginutils/login.c
+++ b/loginutils/login.c
@@ -442,6 +442,9 @@ int login_main(int argc UNUSED_PARAM, char **argv)
}
/* check that the account is healthy */
pamret = pam_acct_mgmt(pamh, 0);
+ if (pamret == PAM_NEW_AUTHTOK_REQD) {
+ pamret = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ }
if (pamret != PAM_SUCCESS) {
failed_msg = "acct_mgmt";
goto pam_auth_failed;
diff --git a/loginutils/su.c b/loginutils/su.c
index 784a53552..e1db7590f 100644
--- a/loginutils/su.c
+++ b/loginutils/su.c
@@ -35,7 +35,7 @@
//kbuild:lib-$(CONFIG_SU) += su.o
//usage:#define su_trivial_usage
-//usage: "[-lmp] [-] [-s SH] [USER [SCRIPT ARGS / -c 'CMD' ARG0 ARGS]]"
+//usage: "[-lmp] [-s SH] [-] [USER [FILE ARGS | -c 'CMD' [ARG0 ARGS]]]"
//usage:#define su_full_usage "\n\n"
//usage: "Run shell under USER (by default, root)\n"
//usage: "\n -,-l Clear environment, go to home dir, run shell as login shell"
diff --git a/miscutils/ascii.c b/miscutils/ascii.c
new file mode 100644
index 000000000..98c11fa51
--- /dev/null
+++ b/miscutils/ascii.c
@@ -0,0 +1,51 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2021 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//config:config ASCII
+//config: bool "ascii"
+//config: default y
+//config: help
+//config: Print ascii table.
+//config:
+
+//applet:IF_ASCII(APPLET(ascii, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_ASCII) += ascii.o
+
+//usage:#define ascii_trivial_usage NOUSAGE_STR
+//usage:#define ascii_full_usage ""
+
+#include "libbb.h"
+
+int ascii_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ascii_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+ const char *ctrl =
+ "NUL""SOH""STX""ETX""EOT""ENQ""ACK""BEL"
+ "BS ""HT ""NL ""VT ""FF ""CR ""SO ""SI "
+ "DLE""DC1""DC2""DC3""DC4""NAK""SYN""ETB"
+ "CAN""EM ""SUB""ESC""FS ""GS ""RS ""US "
+ ;
+//TODO: od has a similar table, can we reuse it?
+ char last[2];
+ unsigned i;
+
+ last[1] = '\0';
+ printf("Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex\n");
+ for (i = 0; i < 16; i++) {
+ printf("%3u %02x %.3s%4u %02x %.3s%4u %02x %c%4u %02x %c%4u %02x %c%4u %02x %c%5u %02x %c%5u %02x %s\n",
+ i+0x00, i+0x00, ctrl + i*3,
+ i+0x10, i+0x10, ctrl + i*3 + 16*3,
+ i+0x20, i+0x20, i+0x20,
+ i+0x30, i+0x30, i+0x30,
+ i+0x40, i+0x40, i+0x40,
+ i+0x50, i+0x50, i+0x50,
+ i+0x60, i+0x60, i+0x60,
+ i+0x70, i+0x70, (i+0x70 == 0x7f ? "DEL" : (last[0] = i+0x70, last))
+ );
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/bc.c b/miscutils/bc.c
index dd9f4f8f1..f9b08b01e 100644
--- a/miscutils/bc.c
+++ b/miscutils/bc.c
@@ -5,7 +5,7 @@
* Original code copyright (c) 2018 Gavin D. Howard and contributors.
*/
//TODO:
-// maybe implement a^b for non-integer b?
+// maybe implement a^b for non-integer b? (see zbc_num_p())
#define DEBUG_LEXER 0
#define DEBUG_COMPILE 0
@@ -108,7 +108,7 @@
//See www.gnu.org/software/bc/manual/bc.html
//usage:#define bc_trivial_usage
-//usage: "[-sqlw] [FILE...]"
+//usage: "[-sqlw] [FILE]..."
//usage:
//usage:#define bc_full_usage "\n"
//usage: "\nArbitrary precision calculator"
@@ -1386,6 +1386,12 @@ static void bc_num_copy(BcNum *d, BcNum *s)
}
}
+static void bc_num_init_and_copy(BcNum *d, BcNum *s)
+{
+ bc_num_init(d, s->len);
+ bc_num_copy(d, s);
+}
+
static BC_STATUS zbc_num_ulong_abs(BcNum *n, unsigned long *result_p)
{
size_t i;
@@ -1985,11 +1991,8 @@ static FAST_FUNC BC_STATUS zbc_num_m(BcNum *a, BcNum *b, BcNum *restrict c, size
scale = BC_MIN(a->rdx + b->rdx, scale);
maxrdx = BC_MAX(maxrdx, scale);
- bc_num_init(&cpa, a->len);
- bc_num_init(&cpb, b->len);
-
- bc_num_copy(&cpa, a);
- bc_num_copy(&cpb, b);
+ bc_num_init_and_copy(&cpa, a);
+ bc_num_init_and_copy(&cpb, b);
cpa.neg = cpb.neg = false;
s = zbc_num_shift(&cpa, maxrdx);
@@ -2152,6 +2155,7 @@ static FAST_FUNC BC_STATUS zbc_num_p(BcNum *a, BcNum *b, BcNum *restrict c, size
BcNum copy;
unsigned long pow;
size_t i, powrdx, resrdx;
+ size_t a_rdx;
bool neg;
// GNU bc does not allow 2^2.0 - we do
@@ -2159,6 +2163,9 @@ static FAST_FUNC BC_STATUS zbc_num_p(BcNum *a, BcNum *b, BcNum *restrict c, size
if (b->num[i] != 0)
RETURN_STATUS(bc_error("not an integer"));
+// a^b for non-integer b (for a>0) can be implemented as exp(ln(a)*b).
+// Possibly better precision would be given by a^int(b) * exp(ln(a)*frac(b)).
+
if (b->len == 0) {
bc_num_one(c);
RETURN_STATUS(BC_STATUS_SUCCESS);
@@ -2180,18 +2187,31 @@ static FAST_FUNC BC_STATUS zbc_num_p(BcNum *a, BcNum *b, BcNum *restrict c, size
if (s) RETURN_STATUS(s);
// b is not used beyond this point
- bc_num_init(&copy, a->len);
- bc_num_copy(&copy, a);
+ bc_num_init_and_copy(&copy, a);
+ a_rdx = a->rdx; // pull it into a CPU register (hopefully)
+ // a is not used beyond this point
if (!neg) {
- if (a->rdx > scale)
- scale = a->rdx;
- if (a->rdx * pow < scale)
- scale = a->rdx * pow;
- }
-
-
- for (powrdx = a->rdx; !(pow & 1); pow >>= 1) {
+ unsigned long new_scale;
+ if (a_rdx > scale)
+ scale = a_rdx;
+ new_scale = a_rdx * pow;
+ // Don't fall for multiplication overflow. Example:
+ // 0.01^2147483648 a_rdx:2 pow:0x80000000, 32bit mul is 0.
+//not that it matters with current algorithm, it would OOM on such large powers,
+//but it can be improved to detect zero results etc. Example: with scale=0,
+//result of 0.01^N for any N>1 is 0: 0.01^2 = 0.0001 ~= 0.00 (trunc to scale)
+//then this would matter:
+ // if a_rdx != 0 and new_scale < pow, we had overflow,
+ // correct "new_scale" value is larger than ULONG_MAX,
+ // thus larger than any possible current value of "scale",
+ // thus "scale = new_scale" should not be done:
+ if (a_rdx == 0 || new_scale >= pow)
+ if (new_scale < scale)
+ scale = new_scale;
+ }
+
+ for (powrdx = a_rdx; !(pow & 1); pow >>= 1) {
powrdx <<= 1;
s = zbc_num_mul(&copy, &copy, &copy, powrdx);
if (s) goto err;
@@ -2493,8 +2513,7 @@ static void bc_array_copy(BcVec *d, const BcVec *s)
dnum = (void*)d->v;
snum = (void*)s->v;
for (i = 0; i < s->len; i++, dnum++, snum++) {
- bc_num_init(dnum, snum->len);
- bc_num_copy(dnum, snum);
+ bc_num_init_and_copy(dnum, snum);
}
}
@@ -2508,8 +2527,7 @@ static void dc_result_copy(BcResult *d, BcResult *src)
case XC_RESULT_IBASE:
case XC_RESULT_SCALE:
case XC_RESULT_OBASE:
- bc_num_init(&d->d.n, src->d.n.len);
- bc_num_copy(&d->d.n, &src->d.n);
+ bc_num_init_and_copy(&d->d.n, &src->d.n);
break;
case XC_RESULT_VAR:
case XC_RESULT_ARRAY:
@@ -5611,11 +5629,10 @@ static BC_STATUS zxc_num_printNum(BcNum *n, unsigned base_t, size_t width, BcNum
}
bc_vec_init(&stack, sizeof(long), NULL);
- bc_num_init(&intp, n->len);
+ bc_num_init_and_copy(&intp, n);
bc_num_init(&fracp, n->rdx);
bc_num_init(&digit, width);
bc_num_init(&frac_len, BC_NUM_INT(n));
- bc_num_copy(&intp, n);
bc_num_one(&frac_len);
base.cap = ARRAY_SIZE(base_digs);
base.num = base_digs;
@@ -5784,8 +5801,7 @@ static BC_STATUS zxc_program_negate(void)
s = zxc_program_prep(&ptr, &num);
if (s) RETURN_STATUS(s);
- bc_num_init(&res.d.n, num->len);
- bc_num_copy(&res.d.n, num);
+ bc_num_init_and_copy(&res.d.n, num);
if (res.d.n.len) res.d.n.neg = !res.d.n.neg;
xc_program_retire(&res, XC_RESULT_TEMP);
@@ -6024,8 +6040,7 @@ static BC_STATUS zxc_program_assign(char inst)
s = BC_STATUS_SUCCESS;
}
- bc_num_init(&res.d.n, l->len);
- bc_num_copy(&res.d.n, l);
+ bc_num_init_and_copy(&res.d.n, l);
xc_program_binOpRetire(&res);
RETURN_STATUS(s);
@@ -6122,8 +6137,7 @@ static BC_STATUS zbc_program_incdec(char inst)
if (inst == BC_INST_INC_POST || inst == BC_INST_DEC_POST) {
copy.t = XC_RESULT_TEMP;
- bc_num_init(&copy.d.n, num->len);
- bc_num_copy(&copy.d.n, num);
+ bc_num_init_and_copy(&copy.d.n, num);
}
res.t = BC_RESULT_ONE;
@@ -6225,8 +6239,7 @@ static BC_STATUS zbc_program_return(char inst)
s = zxc_program_num(operand, &num);
if (s) RETURN_STATUS(s);
- bc_num_init(&res.d.n, num->len);
- bc_num_copy(&res.d.n, num);
+ bc_num_init_and_copy(&res.d.n, num);
bc_vec_pop(&G.prog.results);
} else {
if (f->voidfunc)
diff --git a/miscutils/chat.c b/miscutils/chat.c
index 86a114df6..f9e12a4ac 100644
--- a/miscutils/chat.c
+++ b/miscutils/chat.c
@@ -79,7 +79,7 @@
//kbuild:lib-$(CONFIG_CHAT) += chat.o
//usage:#define chat_trivial_usage
-//usage: "EXPECT [SEND [EXPECT [SEND...]]]"
+//usage: "EXPECT [SEND [EXPECT [SEND]]...]"
//usage:#define chat_full_usage "\n\n"
//usage: "Useful for interacting with a modem connected to stdin/stdout.\n"
//usage: "A script consists of \"expect-send\" argument pairs.\n"
diff --git a/miscutils/flashcp.c b/miscutils/flashcp.c
index 1ca9d158d..93c80cc6c 100644
--- a/miscutils/flashcp.c
+++ b/miscutils/flashcp.c
@@ -19,9 +19,9 @@
//kbuild:lib-$(CONFIG_FLASHCP) += flashcp.o
//usage:#define flashcp_trivial_usage
-//usage: "-v FILE MTD_DEVICE"
+//usage: "[-v] FILE MTD_DEVICE"
//usage:#define flashcp_full_usage "\n\n"
-//usage: "Copy an image to MTD device\n"
+//usage: "Copy FILE to MTD device\n"
//usage: "\n -v Verbose"
#include "libbb.h"
diff --git a/miscutils/i2c_tools.c b/miscutils/i2c_tools.c
index 48135921d..b25d49792 100644
--- a/miscutils/i2c_tools.c
+++ b/miscutils/i2c_tools.c
@@ -1394,7 +1394,7 @@ static void check_i2c_func(int fd)
}
//usage:#define i2ctransfer_trivial_usage
-//usage: "[-fay] I2CBUS {rLENGTH[@ADDR] | wLENGTH[@ADDR] DATA...}..."
+//usage: "[-fay] I2CBUS { rLENGTH[@ADDR] | wLENGTH[@ADDR] DATA...}..."
//usage:#define i2ctransfer_full_usage "\n\n"
//usage: "Read/write I2C data in one transfer"
//usage: "\n"
diff --git a/miscutils/man.c b/miscutils/man.c
index 722f6641e..d319e8bba 100644
--- a/miscutils/man.c
+++ b/miscutils/man.c
@@ -324,7 +324,7 @@ int man_main(int argc UNUSED_PARAM, char **argv)
/* is 1st ARG a SECTION? */
sec_list = conf_sec_list;
- if (is_section_name(conf_sec_list, *argv)) {
+ if (is_section_name(conf_sec_list, *argv) && argv[1]) {
/* yes */
sec_list = *argv++;
}
diff --git a/miscutils/microcom.c b/miscutils/microcom.c
index 399d4cf7f..97b46342f 100644
--- a/miscutils/microcom.c
+++ b/miscutils/microcom.c
@@ -18,14 +18,14 @@
//kbuild:lib-$(CONFIG_MICROCOM) += microcom.o
//usage:#define microcom_trivial_usage
-//usage: "[-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY"
+//usage: "[-d DELAY_MS] [-t TIMEOUT_MS ] [-s SPEED] [-X] TTY"
//usage:#define microcom_full_usage "\n\n"
-//usage: "Copy bytes for stdin to TTY and from TTY to stdout\n"
-//usage: "\n -d Wait up to DELAY ms for TTY output before sending every"
-//usage: "\n next byte to it"
-//usage: "\n -t Exit if both stdin and TTY are silent for TIMEOUT ms"
-//usage: "\n -s Set serial line to SPEED"
-//usage: "\n -X Disable special meaning of NUL and Ctrl-X from stdin"
+//usage: "Copy bytes from stdin to TTY and from TTY to stdout\n"
+//usage: "\n -d DELAY Wait up to DELAY ms for TTY output before sending"
+//usage: "\n every next byte to it"
+//usage: "\n -t TIMEOUT Exit if both stdin and TTY are silent for TIMEOUT ms"
+//usage: "\n -s SPEED Set serial line to SPEED"
+//usage: "\n -X Disable special meaning of NUL and Ctrl-X from stdin"
#include "libbb.h"
#include "common_bufsiz.h"
diff --git a/miscutils/mt.c b/miscutils/mt.c
index 1a4214664..52d5476a1 100644
--- a/miscutils/mt.c
+++ b/miscutils/mt.c
@@ -19,7 +19,7 @@
//usage:#define mt_full_usage "\n\n"
//usage: "Control magnetic tape drive operation\n"
//usage: "\n"
-//usage: "Available Opcodes:\n"
+//usage: "Opcodes:\n"
//usage: "\n"
//usage: "bsf bsfm bsr bss datacompression drvbuffer eof eom erase\n"
//usage: "fsf fsfm fsr fss load lock mkpart nop offline ras1 ras2\n"
diff --git a/miscutils/strings.c b/miscutils/strings.c
index e4a68227e..b01884968 100644
--- a/miscutils/strings.c
+++ b/miscutils/strings.c
@@ -18,7 +18,7 @@
//kbuild:lib-$(CONFIG_STRINGS) += strings.o
//usage:#define strings_trivial_usage
-//usage: "[-fo] [-t o/d/x] [-n LEN] [FILE]..."
+//usage: "[-fo] [-t o|d|x] [-n LEN] [FILE]..."
//usage:#define strings_full_usage "\n\n"
//usage: "Display printable strings in a binary file\n"
//We usually don't bother user with "nop" options. They work, but are not shown:
@@ -26,7 +26,7 @@
//unimplemented alternative is -d: Only strings from initialized, loaded data sections
//usage: "\n -f Precede strings with filenames"
//usage: "\n -o Precede strings with octal offsets"
-//usage: "\n -t o/d/x Precede strings with offsets in base 8/10/16"
+//usage: "\n -t o|d|x Precede strings with offsets in base 8/10/16"
//usage: "\n -n LEN At least LEN characters form a string (default 4)"
#include "libbb.h"
diff --git a/miscutils/ts.c b/miscutils/ts.c
index 6e5d77bda..c7e477cc5 100644
--- a/miscutils/ts.c
+++ b/miscutils/ts.c
@@ -13,7 +13,10 @@
//usage:#define ts_trivial_usage
//usage: "[-is] [STRFTIME]"
-//usage:#define ts_full_usage ""
+//usage:#define ts_full_usage "\n\n"
+//usage: "Pipe stdin to stdout, add timestamp to each line\n"
+//usage: "\n -s Time since start"
+//usage: "\n -i Time since previous line"
#include "libbb.h"
#include "common_bufsiz.h"
diff --git a/miscutils/ubi_tools.c b/miscutils/ubi_tools.c
index 69ead7a13..6d49f61d9 100644
--- a/miscutils/ubi_tools.c
+++ b/miscutils/ubi_tools.c
@@ -251,7 +251,7 @@ int ubi_tools_main(int argc UNUSED_PARAM, char **argv)
} else
//usage:#define ubirmvol_trivial_usage
-//usage: "-n VOLID / -N VOLNAME UBI_DEVICE"
+//usage: "-n VOLID | -N VOLNAME UBI_DEVICE"
//usage:#define ubirmvol_full_usage "\n\n"
//usage: "Remove UBI volume\n"
//usage: "\n -n VOLID Volume ID"
diff --git a/modutils/modinfo.c b/modutils/modinfo.c
index d15772f0d..0a86c3296 100644
--- a/modutils/modinfo.c
+++ b/modutils/modinfo.c
@@ -131,7 +131,7 @@ static void modinfo(const char *path, const char *version,
//usage: "\n -p Shortcut for '-F parm'"
////usage: "\n -n Shortcut for '-F filename'"
//usage: "\n -F keyword Keyword to look for"
-//usage: "\n -0 Separate output with NULs"
+//usage: "\n -0 NUL terminated output"
//usage:#define modinfo_example_usage
//usage: "$ modinfo -F vermagic loop\n"
diff --git a/modutils/modprobe.c b/modutils/modprobe.c
index c334186b8..235706fd5 100644
--- a/modutils/modprobe.c
+++ b/modutils/modprobe.c
@@ -629,8 +629,9 @@ int modprobe_main(int argc UNUSED_PARAM, char **argv)
config_close(parser);
parser = config_open2("modules.builtin", fopen_for_read);
+ /* this file contains lines like "kernel/fs/binfmt_script.ko" */
while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL))
- get_or_add_modentry(s)->flags |= MODULE_FLAG_BUILTIN;
+ get_or_add_modentry(bb_basename(s))->flags |= MODULE_FLAG_BUILTIN;
config_close(parser);
}
diff --git a/networking/ftpd.c b/networking/ftpd.c
index 6ca231c90..0d6a289c7 100644
--- a/networking/ftpd.c
+++ b/networking/ftpd.c
@@ -54,7 +54,7 @@
//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
//usage:#define ftpd_trivial_usage
-//usage: "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]"
+//usage: "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t SEC] [-T SEC] [DIR]"
//usage:#define ftpd_full_usage "\n\n"
//usage: IF_NOT_FEATURE_FTPD_AUTHENTICATION(
//usage: "Anonymous FTP server. Client access occurs under ftpd's UID.\n"
@@ -63,7 +63,7 @@
//usage: "FTP server. "
//usage: )
//usage: "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
-//usage: "Should be used as inetd service, inetd.conf line:\n"
+//usage: "It is an inetd service, inetd.conf line:\n"
//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
//usage: "Can be run from tcpsvd:\n"
//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve"
diff --git a/networking/httpd_post_upload.cgi b/networking/httpd_post_upload.cgi
index e4ffd2bb5..538f7181b 100755
--- a/networking/httpd_post_upload.cgi
+++ b/networking/httpd_post_upload.cgi
@@ -18,7 +18,7 @@
# ^M <--------- extra empty line
# -----------------------------29995809218093749221856446032--^M
-file=/tmp/$$-$RANDOM
+file=$(mktemp)
CR=`printf '\r'`
diff --git a/networking/nameif.c b/networking/nameif.c
index 854594c83..66e042688 100644
--- a/networking/nameif.c
+++ b/networking/nameif.c
@@ -52,10 +52,10 @@
//usage:#define nameif_full_usage "\n\n"
//usage: "Rename network interface while it in the down state."
//usage: IF_NOT_FEATURE_NAMEIF_EXTENDED(
-//usage: "\nThe device with address HWADDR is renamed to IFACE."
+//usage: "\nThe device with address HWADDR is renamed to IFNAME."
//usage: )
//usage: IF_FEATURE_NAMEIF_EXTENDED(
-//usage: "\nThe device matched by SELECTOR is renamed to IFACE."
+//usage: "\nThe device matched by SELECTOR is renamed to IFNAME."
//usage: "\nSELECTOR can be a combination of:"
//usage: "\n driver=STRING"
//usage: "\n bus=STRING"
diff --git a/networking/nslookup.c b/networking/nslookup.c
index c43ad46f3..de7b5c0e7 100644
--- a/networking/nslookup.c
+++ b/networking/nslookup.c
@@ -25,7 +25,7 @@
//usage:#define nslookup_full_usage "\n\n"
//usage: "Query DNS about HOST"
//usage: IF_FEATURE_NSLOOKUP_BIG("\n")
-//usage: IF_FEATURE_NSLOOKUP_BIG("\nQUERY_TYPE: soa,ns,a,"IF_FEATURE_IPV6("aaaa,")"cname,mx,txt,ptr,any")
+//usage: IF_FEATURE_NSLOOKUP_BIG("\nQUERY_TYPE: soa,ns,a,"IF_FEATURE_IPV6("aaaa,")"cname,mx,txt,ptr,srv,any")
//usage:#define nslookup_example_usage
//usage: "$ nslookup localhost\n"
//usage: "Server: default\n"
diff --git a/networking/tc.c b/networking/tc.c
index 510684443..46ad23d8b 100644
--- a/networking/tc.c
+++ b/networking/tc.c
@@ -111,16 +111,14 @@ static char* print_tc_classid(uint32_t cid)
#if 0 /* IMPOSSIBLE */
if (cid == TC_H_ROOT)
return xasprintf("root");
- else
#endif
if (cid == TC_H_UNSPEC)
return xasprintf("none");
- else if (TC_H_MAJ(cid) == 0)
+ if (TC_H_MAJ(cid) == 0)
return xasprintf(":%x", TC_H_MIN(cid));
- else if (TC_H_MIN(cid) == 0)
+ if (TC_H_MIN(cid) == 0)
return xasprintf("%x:", TC_H_MAJ(cid)>>16);
- else
- return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid));
+ return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid));
}
/* Get a qdisc handle. Return 0 on success, !0 otherwise. */
@@ -376,8 +374,10 @@ static FAST_FUNC int print_qdisc(
prio_print_opt(tb[TCA_OPTIONS]);
} else if (qqq == 1) { /* class based queuing */
cbq_print_opt(tb[TCA_OPTIONS]);
- } else
- bb_error_msg("unknown %s", name);
+ } else {
+ /* don't know how to print options for this qdisc */
+ printf("(options for %s)", name);
+ }
}
bb_putchar('\n');
return 0;
@@ -405,7 +405,7 @@ static FAST_FUNC int print_class(
return -1;
}
/* not the desired interface? */
- if (filter_qdisc && TC_H_MAJ(msg->tcm_handle^filter_qdisc))
+ if (filter_qdisc && TC_H_MAJ(msg->tcm_handle ^ filter_qdisc))
return 0;
memset (tb, 0, sizeof(tb));
parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
@@ -418,8 +418,8 @@ static FAST_FUNC int print_class(
name = (char*)RTA_DATA(tb[TCA_KIND]);
classid = !msg->tcm_handle ? NULL : print_tc_classid(
- filter_qdisc ? TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
- printf ("class %s %s", name, classid);
+ filter_qdisc ? TC_H_MIN(msg->tcm_handle) : msg->tcm_handle);
+ printf ("class %s %s ", name, classid);
if (ENABLE_FEATURE_CLEAN_UP)
free(classid);
@@ -445,8 +445,10 @@ static FAST_FUNC int print_class(
} else if (qqq == 1) { /* class based queuing */
/* cbq_print_copt() is identical to cbq_print_opt(). */
cbq_print_opt(tb[TCA_OPTIONS]);
- } else
- bb_error_msg("unknown %s", name);
+ } else {
+ /* don't know how to print options for this class */
+ printf("(options for %s)", name);
+ }
}
bb_putchar('\n');
@@ -511,12 +513,11 @@ int tc_main(int argc UNUSED_PARAM, char **argv)
ret = EXIT_SUCCESS;
obj = index_in_substrings(objects, *argv++);
-
if (obj < 0)
bb_show_usage();
- if (!*argv)
- cmd = CMD_show; /* list is the default */
- else {
+
+ cmd = CMD_show; /* list (aka show) is the default */
+ if (*argv) {
cmd = index_in_substrings(commands, *argv);
if (cmd < 0)
invarg_1_to_2(*argv, argv[-1]);
@@ -538,16 +539,17 @@ int tc_main(int argc UNUSED_PARAM, char **argv)
msg.tcm_ifindex = xll_name_to_index(dev);
if (cmd >= CMD_show)
filter_ifindex = msg.tcm_ifindex;
- } else
- if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show)
- || (arg == ARG_handle && obj == OBJ_qdisc && cmd == CMD_change)
+ continue;
+ }
+ if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show) /* tc class show|list qdisc HANDLE */
+ || (arg == ARG_handle && obj == OBJ_qdisc && cmd == CMD_change) /* tc qdisc change handle HANDLE */
) {
NEXT_ARG();
/* We don't care about duparg2("qdisc handle",*argv) for now */
if (get_qdisc_handle(&filter_qdisc, *argv))
invarg_1_to_2(*argv, "qdisc");
} else
- if (obj != OBJ_qdisc
+ if (obj != OBJ_qdisc /* tc class|filter root|parent | tc filter preference|priority|protocol */
&& (arg == ARG_root
|| arg == ARG_parent
|| (obj == OBJ_filter && arg >= ARG_pref)
diff --git a/networking/tcpudp.c b/networking/tcpudp.c
index 8c4afabf6..708e05c2e 100644
--- a/networking/tcpudp.c
+++ b/networking/tcpudp.c
@@ -216,17 +216,25 @@ enum {
OPT_K = (1 << 16),
};
-static void connection_status(void)
+static void if_verbose_print_connection_status(void)
{
- /* "only 1 client max" desn't need this */
- if (cmax > 1)
- bb_error_msg("status %u/%u", cnum, cmax);
+ if (verbose) {
+ /* "only 1 client max" desn't need this */
+ if (cmax > 1)
+ bb_error_msg("status %u/%u", cnum, cmax);
+ }
}
+/* SIGCHLD handler is reentrancy-safe because SIGCHLD is unmasked
+ * only over accept() or recvfrom() calls, not over memory allocations
+ * or printouts. Do need to save/restore errno in order not to mangle
+ * these syscalls' error code, if any.
+ */
static void sig_child_handler(int sig UNUSED_PARAM)
{
int wstat;
pid_t pid;
+ int sv_errno = errno;
while ((pid = wait_any_nohang(&wstat)) > 0) {
if (max_per_host)
@@ -236,8 +244,8 @@ static void sig_child_handler(int sig UNUSED_PARAM)
if (verbose)
print_waitstat(pid, wstat);
}
- if (verbose)
- connection_status();
+ if_verbose_print_connection_status();
+ errno = sv_errno;
}
int tcpudpsvd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -458,7 +466,7 @@ int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv)
xconnect(0, &remote.u.sa, sa_len);
/* hole? at this point we have no wildcard udp socket...
* can this cause clients to get "port unreachable" icmp?
- * Yup, time window is very small, but it exists (is it?) */
+ * Yup, time window is very small, but it exists (does it?) */
/* ..."open new socket", continued */
xbind(sock, &lsa->u.sa, sa_len);
socket_want_pktinfo(sock);
@@ -491,8 +499,7 @@ int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv)
if (pid != 0) {
/* Parent */
cnum++;
- if (verbose)
- connection_status();
+ if_verbose_print_connection_status();
if (hccp)
hccp->pid = pid;
/* clean up changes done by vforked child */
@@ -586,8 +593,14 @@ int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv)
xdup2(0, 1);
+ /* Restore signal handling for the to-be-execed process */
signal(SIGPIPE, SIG_DFL); /* this one was SIG_IGNed */
- /* Non-ignored signals revert to SIG_DFL on exec anyway */
+ /* Non-ignored signals revert to SIG_DFL on exec anyway
+ * But we can get signals BEFORE execvp(), this is unlikely
+ * but it would invoke sig_child_handler(), which would
+ * check waitpid(WNOHANG), then print "status N/M" if verbose.
+ * I guess we can live with that possibility.
+ */
/*signal(SIGCHLD, SIG_DFL);*/
sig_unblock(SIGCHLD);
diff --git a/networking/telnetd.c b/networking/telnetd.c
index 29f805de7..de4d733f9 100644
--- a/networking/telnetd.c
+++ b/networking/telnetd.c
@@ -109,6 +109,7 @@
//usage: "\n -i Inetd mode"
//usage: IF_FEATURE_TELNETD_INETD_WAIT(
//usage: "\n -w SEC Inetd 'wait' mode, linger time SEC"
+//usage: "\n inetd.conf line: 23 stream tcp wait root telnetd telnetd -w10"
//usage: "\n -S Log to syslog (implied by -i or without -F and -w)"
//usage: )
//usage: )
diff --git a/networking/tftp.c b/networking/tftp.c
index 4b86ed9de..f5b4367ca 100644
--- a/networking/tftp.c
+++ b/networking/tftp.c
@@ -113,10 +113,9 @@
//usage:#define tftpd_full_usage "\n\n"
//usage: "Transfer a file on tftp client's request\n"
//usage: "\n"
-//usage: "tftpd should be used as an inetd service.\n"
-//usage: "tftpd's line for inetd.conf:\n"
+//usage: "tftpd is an inetd service, inetd.conf line:\n"
//usage: " 69 dgram udp nowait root tftpd tftpd -l /files/to/serve\n"
-//usage: "It also can be ran from udpsvd:\n"
+//usage: "Can be run from udpsvd:\n"
//usage: " udpsvd -vE 0.0.0.0 69 tftpd /files/to/serve\n"
//usage: "\n -r Prohibit upload"
//usage: "\n -c Allow file creation via upload"
diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c
index a395e838d..a11c4e841 100644
--- a/networking/udhcp/arpping.c
+++ b/networking/udhcp/arpping.c
@@ -47,6 +47,7 @@ int FAST_FUNC arpping(uint32_t test_nip,
int rv = 1; /* "no reply received" yet */
struct sockaddr addr; /* for interface name */
struct arpMsg arp;
+ const char *msg;
if (!timeo)
return 1;
@@ -58,7 +59,7 @@ int FAST_FUNC arpping(uint32_t test_nip,
}
if (setsockopt_broadcast(s) == -1) {
- bb_simple_perror_msg("can't enable bcast on raw socket");
+ bb_simple_perror_msg("can't enable bcast on ARP socket");
goto ret;
}
@@ -131,6 +132,9 @@ int FAST_FUNC arpping(uint32_t test_nip,
ret:
close(s);
- log1("%srp reply received for this address", rv ? "no a" : "A");
+ msg = "no ARP reply received for this address";
+ if (rv == 0)
+ msg += 3;
+ log1s(msg);
return rv;
}
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
index f2d6907ad..31e525cb0 100644
--- a/networking/udhcp/common.c
+++ b/networking/udhcp/common.c
@@ -49,6 +49,7 @@ const struct dhcp_optflag dhcp_optflags[] = {
{ OPTION_U32 , 0x33 }, /* DHCP_LEASE_TIME */
{ OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */
{ OPTION_STRING , 0x38 }, /* DHCP_ERR_MESSAGE */
+ { OPTION_STRING , 0x3c }, /* DHCP_VENDOR */
//TODO: must be combined with 'sname' and 'file' handling:
{ OPTION_STRING_HOST , 0x42 }, /* DHCP_TFTP_SERVER_NAME */
{ OPTION_STRING , 0x43 }, /* DHCP_BOOT_FILE */
@@ -83,7 +84,6 @@ const struct dhcp_optflag dhcp_optflags[] = {
{ OPTION_U8 , 0x35 }, /* DHCP_MESSAGE_TYPE */
{ OPTION_U16 , 0x39 }, /* DHCP_MAX_SIZE */
//looks like these opts will work just fine even without these defs:
-// { OPTION_STRING , 0x3c }, /* DHCP_VENDOR */
// /* not really a string: */
// { OPTION_STRING , 0x3d }, /* DHCP_CLIENT_ID */
{ 0, 0 } /* zeroed terminating entry */
@@ -120,6 +120,7 @@ const char dhcp_option_strings[] ALIGN1 =
"lease" "\0" /* DHCP_LEASE_TIME */
"serverid" "\0" /* DHCP_SERVER_ID */
"message" "\0" /* DHCP_ERR_MESSAGE */
+ "vendor" "\0" /* DHCP_VENDOR */
"tftp" "\0" /* DHCP_TFTP_SERVER_NAME*/
"bootfile" "\0" /* DHCP_BOOT_FILE */
// "userclass" "\0" /* DHCP_USER_CLASS */
@@ -184,6 +185,13 @@ const uint8_t dhcp_option_lengths[] ALIGN1 = {
*/
};
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+void FAST_FUNC log1s(const char *msg)
+{
+ if (dhcp_verbose >= 1)
+ bb_simple_info_msg(msg);
+}
+#endif
#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
static void log_option(const char *pfx, const uint8_t *opt)
@@ -420,6 +428,40 @@ int FAST_FUNC udhcp_str2nip(const char *str, void *arg)
return 1;
}
+void* FAST_FUNC udhcp_insert_new_option(
+ struct option_set **opt_list,
+ unsigned code,
+ unsigned length,
+ bool dhcpv6)
+{
+ IF_NOT_UDHCPC6(bool dhcpv6 = 0;)
+ struct option_set *new, **curr;
+
+ log2("attaching option %02x to list", code);
+ new = xmalloc(sizeof(*new));
+ if (!dhcpv6) {
+ new->data = xzalloc(length + OPT_DATA);
+ new->data[OPT_CODE] = code;
+ new->data[OPT_LEN] = length;
+ } else {
+ new->data = xzalloc(length + D6_OPT_DATA);
+ new->data[D6_OPT_CODE] = code >> 8;
+ new->data[D6_OPT_CODE + 1] = code & 0xff;
+ new->data[D6_OPT_LEN] = length >> 8;
+ new->data[D6_OPT_LEN + 1] = length & 0xff;
+ }
+
+ curr = opt_list;
+//FIXME: DHCP6 codes > 255!!
+ while (*curr && (*curr)->data[OPT_CODE] < code)
+ curr = &(*curr)->next;
+
+ new->next = *curr;
+ *curr = new;
+
+ return new->data;
+}
+
/* udhcp_str2optset:
* Parse string option representation to binary form and add it to opt_list.
* Called to parse "udhcpc -x OPTNAME:OPTVAL"
@@ -459,32 +501,12 @@ static NOINLINE void attach_option(
existing = udhcp_find_option(*opt_list, optflag->code);
if (!existing) {
- struct option_set *new, **curr;
-
/* make a new option */
- log2("attaching option %02x to list", optflag->code);
- new = xmalloc(sizeof(*new));
- if (!dhcpv6) {
- new->data = xmalloc(length + OPT_DATA);
- new->data[OPT_CODE] = optflag->code;
- new->data[OPT_LEN] = length;
- memcpy(new->data + OPT_DATA, buffer, length);
- } else {
- new->data = xmalloc(length + D6_OPT_DATA);
- new->data[D6_OPT_CODE] = optflag->code >> 8;
- new->data[D6_OPT_CODE + 1] = optflag->code & 0xff;
- new->data[D6_OPT_LEN] = length >> 8;
- new->data[D6_OPT_LEN + 1] = length & 0xff;
- memcpy(new->data + D6_OPT_DATA, buffer,
- length);
- }
-
- curr = opt_list;
- while (*curr && (*curr)->data[OPT_CODE] < optflag->code)
- curr = &(*curr)->next;
-
- new->next = *curr;
- *curr = new;
+ uint8_t *p = udhcp_insert_new_option(opt_list, optflag->code, length, dhcpv6);
+ if (!dhcpv6)
+ memcpy(p + OPT_DATA, buffer, length);
+ else
+ memcpy(p + D6_OPT_DATA, buffer, length);
goto ret;
}
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index cc0abd269..8c678dd32 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -275,7 +275,8 @@ struct option_set *udhcp_find_option(struct option_set *opt_list, uint8_t code)
# define IF_UDHCP_VERBOSE(...) __VA_ARGS__
extern unsigned dhcp_verbose;
# define log1(...) do { if (dhcp_verbose >= 1) bb_info_msg(__VA_ARGS__); } while (0)
-# define log1s(msg) do { if (dhcp_verbose >= 1) bb_simple_info_msg(msg); } while (0)
+//# define log1s(msg) do { if (dhcp_verbose >= 1) bb_simple_info_msg(msg); } while (0)
+void log1s(const char *msg) FAST_FUNC;
# if CONFIG_UDHCP_DEBUG >= 2
void udhcp_dump_packet(struct dhcp_packet *packet) FAST_FUNC;
# define log2(...) do { if (dhcp_verbose >= 2) bb_info_msg(__VA_ARGS__); } while (0)
@@ -319,6 +320,16 @@ void udhcp_dump_packet(struct dhcp_packet *packet) FAST_FUNC;
/* 2nd param is "uint32_t*" */
int FAST_FUNC udhcp_str2nip(const char *str, void *arg);
+
+#if !ENABLE_UDHCPC6
+#define udhcp_insert_new_option(opt_list, code, length, dhcpv6) \
+ udhcp_insert_new_option(opt_list, code, length)
+#endif
+void* FAST_FUNC udhcp_insert_new_option(struct option_set **opt_list,
+ unsigned code,
+ unsigned length,
+ bool dhcpv6);
+
/* 2nd param is "struct option_set**" */
#if !ENABLE_UDHCPC6
#define udhcp_str2optset(str, arg, optflags, option_strings, dhcpv6) \
diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c
index 0a5cae310..8d11a7539 100644
--- a/networking/udhcp/d6_dhcpc.c
+++ b/networking/udhcp/d6_dhcpc.c
@@ -441,7 +441,7 @@ static char **fill_envp(const uint8_t *option, const uint8_t *option_end)
return envp;
}
-/* Call a script with a par file and env vars */
+/* Call a script with env vars */
static void d6_run_script(const uint8_t *option, const uint8_t *option_end,
const char *name)
{
@@ -464,7 +464,7 @@ static void d6_run_script(const uint8_t *option, const uint8_t *option_end,
free(envp);
}
-/* Call a script with a par file and no env var */
+/* Call a script with no env var */
static void d6_run_script_no_option(const char *name)
{
d6_run_script(NULL, NULL, name);
@@ -479,16 +479,15 @@ static ALWAYS_INLINE uint32_t random_xid(void)
}
/* Initialize the packet with the proper defaults */
-static uint8_t *init_d6_packet(struct d6_packet *packet, char type, uint32_t xid)
+static uint8_t *init_d6_packet(struct d6_packet *packet, char type)
{
uint8_t *ptr;
- struct d6_option *clientid;
unsigned secs;
memset(packet, 0, sizeof(*packet));
- packet->d6_xid32 = xid;
- packet->d6_msg_type = type;
+ packet->d6_xid32 = client_data.xid;
+ packet->d6_msg_type = type; /* union, overwrites lowest byte of d6_xid32 */
/* ELAPSED_TIME option is required to be present by the RFC,
* and some servers do check for its presense. [which?]
@@ -503,9 +502,7 @@ static uint8_t *init_d6_packet(struct d6_packet *packet, char type, uint32_t xid
*((uint16_t*)ptr) = (secs < 0xffff) ? htons(secs) : 0xffff;
ptr += 2;
- /* add CLIENTID option */
- clientid = (void*)client_data.clientid;
- return mempcpy(ptr, clientid, clientid->len + 2+2);
+ return ptr;
}
static uint8_t *add_d6_client_options(uint8_t *ptr)
@@ -588,15 +585,15 @@ static int d6_mcast_from_client_data_ifindex(struct d6_packet *packet, uint8_t *
* about parameter values the client would like to have returned.
*/
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_d6_info_request(uint32_t xid)
+static NOINLINE int send_d6_info_request(void)
{
struct d6_packet packet;
uint8_t *opt_ptr;
- /* Fill in: msg type, client id */
- opt_ptr = init_d6_packet(&packet, D6_MSG_INFORMATION_REQUEST, xid);
+ /* Fill in: msg type, xid, ELAPSED_TIME */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_INFORMATION_REQUEST);
- /* Add options:
+ /* Add options: client-id,
* "param req" option according to -O, options specified with -x
*/
opt_ptr = add_d6_client_options(opt_ptr);
@@ -687,14 +684,14 @@ static NOINLINE int send_d6_info_request(uint32_t xid)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ipv6)
+static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6)
{
struct d6_packet packet;
uint8_t *opt_ptr;
unsigned len;
- /* Fill in: msg type, client id */
- opt_ptr = init_d6_packet(&packet, D6_MSG_SOLICIT, xid);
+ /* Fill in: msg type, xid, ELAPSED_TIME */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_SOLICIT);
/* Create new IA_NA, optionally with included IAADDR with requested IP */
free(client6_data.ia_na);
@@ -726,7 +723,7 @@ static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ip
opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, len);
}
- /* Add options:
+ /* Add options: client-id,
* "param req" option according to -O, options specified with -x
*/
opt_ptr = add_d6_client_options(opt_ptr);
@@ -766,13 +763,13 @@ static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ip
* messages from the server.
*/
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_d6_select(uint32_t xid)
+static NOINLINE int send_d6_select(void)
{
struct d6_packet packet;
uint8_t *opt_ptr;
- /* Fill in: msg type, client id */
- opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST, xid);
+ /* Fill in: msg type, xid, ELAPSED_TIME */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST);
/* server id */
opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
@@ -783,7 +780,7 @@ static NOINLINE int send_d6_select(uint32_t xid)
if (client6_data.ia_pd)
opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
- /* Add options:
+ /* Add options: client-id,
* "param req" option according to -O, options specified with -x
*/
opt_ptr = add_d6_client_options(opt_ptr);
@@ -839,13 +836,13 @@ static NOINLINE int send_d6_select(uint32_t xid)
* about parameter values the client would like to have returned.
*/
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_d6_renew(uint32_t xid, struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
+static NOINLINE int send_d6_renew(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
{
struct d6_packet packet;
uint8_t *opt_ptr;
- /* Fill in: msg type, client id */
- opt_ptr = init_d6_packet(&packet, DHCPREQUEST, xid);
+ /* Fill in: msg type, xid, ELAPSED_TIME */
+ opt_ptr = init_d6_packet(&packet, DHCPREQUEST);
/* server id */
opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
@@ -856,7 +853,7 @@ static NOINLINE int send_d6_renew(uint32_t xid, struct in6_addr *server_ipv6, st
if (client6_data.ia_pd)
opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
- /* Add options:
+ /* Add options: client-id,
* "param req" option according to -O, options specified with -x
*/
opt_ptr = add_d6_client_options(opt_ptr);
@@ -878,9 +875,10 @@ int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
{
struct d6_packet packet;
uint8_t *opt_ptr;
+ struct option_set *ci;
- /* Fill in: msg type, client id */
- opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE, random_xid());
+ /* Fill in: msg type, xid, ELAPSED_TIME */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE);
/* server id */
opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
/* IA NA (contains our current IP) */
@@ -889,6 +887,10 @@ int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
/* IA PD */
if (client6_data.ia_pd)
opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
+ /* Client-id */
+ ci = udhcp_find_option(client_data.options, D6_OPT_CLIENTID);
+ if (ci)
+ opt_ptr = mempcpy(opt_ptr, ci->data, D6_OPT_DATA + 2+2 + 6);
bb_info_msg("sending %s", "release");
return d6_send_kernel_packet_from_client_data_ifindex(
@@ -952,7 +954,8 @@ static NOINLINE int d6_recv_raw_packet(struct in6_addr *peer_ipv6, struct d6_pac
if (peer_ipv6)
*peer_ipv6 = packet.ip6.ip6_src; /* struct copy */
- log1("received %s", "a packet");
+ log2("received %s", "a packet");
+ /* log2 because more informative msg for valid packets is printed later at log1 level */
d6_dump_packet(&packet.data);
bytes -= sizeof(packet.ip6) + sizeof(packet.udp);
@@ -1060,9 +1063,6 @@ static int d6_raw_socket(int ifindex)
log1("attached filter to raw socket fd %d", fd); // log?
}
#endif
-
- log1s("created raw socket");
-
return fd;
}
@@ -1088,6 +1088,8 @@ static void change_listen_mode(int new_mode)
static void perform_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
{
+ change_listen_mode(LISTEN_NONE);
+
/* send release packet */
if (client_data.state == BOUND
|| client_data.state == RENEWING
@@ -1095,6 +1097,7 @@ static void perform_d6_release(struct in6_addr *server_ipv6, struct in6_addr *ou
|| client_data.state == RENEW_REQUESTED
) {
bb_simple_info_msg("unicasting a release");
+ client_data.xid = random_xid(); //TODO: can omit?
send_d6_release(server_ipv6, our_cur_ipv6); /* unicast */
}
bb_simple_info_msg("entering released state");
@@ -1105,21 +1108,9 @@ static void perform_d6_release(struct in6_addr *server_ipv6, struct in6_addr *ou
* of the states above.
*/
d6_run_script_no_option("deconfig");
- change_listen_mode(LISTEN_NONE);
client_data.state = RELEASED;
}
-///static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
-///{
-/// uint8_t *storage;
-/// int len = strnlen(str, 255);
-/// storage = xzalloc(len + extra + OPT_DATA);
-/// storage[OPT_CODE] = code;
-/// storage[OPT_LEN] = len + extra;
-/// memcpy(storage + extra + OPT_DATA, str, len);
-/// return storage;
-///}
-
#if BB_MMU
static void client_background(void)
{
@@ -1136,23 +1127,23 @@ static void client_background(void)
//usage:# define IF_UDHCP_VERBOSE(...)
//usage:#endif
//usage:#define udhcpc6_trivial_usage
-//usage: "[-fbnq"IF_UDHCP_VERBOSE("v")"odR] [-i IFACE] [-r IPv6] [-s PROG] [-p PIDFILE]\n"
-//usage: " [-x OPT:VAL]... [-O OPT]..." IF_FEATURE_UDHCP_PORT(" [-P PORT]")
+//usage: "[-fbq"IF_UDHCP_VERBOSE("v")"R] [-t N] [-T SEC] [-A SEC|-n] [-i IFACE] [-s PROG]\n"
+//usage: " [-p PIDFILE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-ldo] [-r IPv6] [-x OPT:VAL]... [-O OPT]..."
//usage:#define udhcpc6_full_usage "\n"
//usage: "\n -i IFACE Interface to use (default "CONFIG_UDHCPC_DEFAULT_INTERFACE")"
//usage: "\n -p FILE Create pidfile"
//usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")"
//usage: "\n -B Request broadcast replies"
//usage: "\n -t N Send up to N discover packets"
-//usage: "\n -T N Pause between packets (default 3 seconds)"
-//usage: "\n -A N Wait N seconds (default 20) after failure"
-//usage: "\n -f Run in foreground"
+//usage: "\n -T SEC Pause between packets (default 3)"
+//usage: "\n -A SEC Wait if lease is not obtained (default 20)"
//usage: USE_FOR_MMU(
//usage: "\n -b Background if lease is not obtained"
//usage: )
//usage: "\n -n Exit if lease is not obtained"
//usage: "\n -q Exit after obtaining lease"
//usage: "\n -R Release IP on exit"
+//usage: "\n -f Run in foreground"
//usage: "\n -S Log to syslog too"
//usage: IF_FEATURE_UDHCP_PORT(
//usage: "\n -P PORT Use PORT (default 546)"
@@ -1160,12 +1151,12 @@ static void client_background(void)
////usage: IF_FEATURE_UDHCPC_ARPING(
////usage: "\n -a Use arping to validate offered address"
////usage: )
-//usage: "\n -O OPT Request option OPT from server (cumulative)"
-//usage: "\n -o Don't request any options (unless -O is given)"
-//usage: "\n -r IPv6 Request this address ('no' to not request any IP)"
-//usage: "\n -d Request prefix"
//usage: "\n -l Send 'information request' instead of 'solicit'"
//usage: "\n (used for servers which do not assign IPv6 addresses)"
+//usage: "\n -r IPv6 Request this address ('no' to not request any IP)"
+//usage: "\n -d Request prefix"
+//usage: "\n -o Don't request any options (unless -O is given)"
+//usage: "\n -O OPT Request option OPT from server (cumulative)"
//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)"
//usage: "\n Examples of string, numeric, and hex byte opts:"
//usage: "\n -x hostname:bbox - option 12"
@@ -1184,7 +1175,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
{
const char *str_r;
IF_FEATURE_UDHCP_PORT(char *str_P;)
- void *clientid_mac_ptr;
+ uint8_t *clientid_mac_ptr;
llist_t *list_O = NULL;
llist_t *list_x = NULL;
int tryagain_timeout = 20;
@@ -1193,7 +1184,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
struct in6_addr srv6_buf;
struct in6_addr ipv6_buf;
struct in6_addr *requested_ipv6;
- uint32_t xid = 0;
int packet_num;
int timeout; /* must be signed */
int lease_remaining; /* must be signed */
@@ -1281,6 +1271,18 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
free(optstr);
}
+ clientid_mac_ptr = NULL;
+ if (!udhcp_find_option(client_data.options, D6_OPT_CLIENTID)) {
+ /* not set, set the default client ID */
+ clientid_mac_ptr = udhcp_insert_new_option(
+ &client_data.options, D6_OPT_CLIENTID,
+ 2+2 + 6, /*dhcp6:*/ 1);
+ clientid_mac_ptr += 2+2; /* skip option code, len */
+ clientid_mac_ptr[1] = 3; /* DUID-LL */
+ clientid_mac_ptr[3] = 1; /* type: ethernet */
+ clientid_mac_ptr += 2+2; /* skip DUID-LL, ethernet */
+ }
+
if (d6_read_interface(client_data.interface,
&client_data.ifindex,
&client6_data.ll_ip6,
@@ -1289,19 +1291,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
return 1;
}
- /* Create client ID based on mac, set clientid_mac_ptr */
- {
- struct d6_option *clientid;
- clientid = xzalloc(2+2+2+2+6);
- clientid->code = D6_OPT_CLIENTID;
- clientid->len = 2+2+6;
- clientid->data[1] = 3; /* DUID-LL */
- clientid->data[3] = 1; /* ethernet */
- clientid_mac_ptr = clientid->data + 2+2;
- memcpy(clientid_mac_ptr, client_data.client_mac, 6);
- client_data.clientid = (void*)clientid;
- }
-
#if !BB_MMU
/* on NOMMU reexec (i.e., background) early */
if (!(opt & OPT_f)) {
@@ -1321,7 +1310,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
client_data.state = INIT_SELECTING;
d6_run_script_no_option("deconfig");
- change_listen_mode(LISTEN_RAW);
packet_num = 0;
timeout = 0;
lease_remaining = 0;
@@ -1356,14 +1344,17 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
log1("waiting %u seconds", timeout);
diff = (unsigned)monotonic_sec();
retval = poll(pfds, 2, timeout * 1000);
+ diff = (unsigned)monotonic_sec() - diff;
+ lease_remaining -= diff;
+ if (lease_remaining < 0)
+ lease_remaining = 0;
+ timeout -= diff;
+ if (timeout < 0)
+ timeout = 0;
+
if (retval < 0) {
/* EINTR? A signal was caught, don't panic */
if (errno == EINTR) {
- diff = (unsigned)monotonic_sec() - diff;
- lease_remaining -= diff;
- if (lease_remaining < 0)
- lease_remaining = 0;
- timeout -= diff;
continue;
}
/* Else: an error occured, panic! */
@@ -1388,23 +1379,27 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
goto ret0; /* iface is gone? */
}
- memcpy(clientid_mac_ptr, client_data.client_mac, 6);
+ if (clientid_mac_ptr)
+ memcpy(clientid_mac_ptr, client_data.client_mac, 6);
switch (client_data.state) {
case INIT_SELECTING:
if (!discover_retries || packet_num < discover_retries) {
- if (packet_num == 0)
- xid = random_xid();
+ if (packet_num == 0) {
+ change_listen_mode(LISTEN_RAW);
+ client_data.xid = random_xid();
+ }
/* multicast */
if (opt & OPT_l)
- send_d6_info_request(xid);
+ send_d6_info_request();
else
- send_d6_discover(xid, requested_ipv6);
+ send_d6_discover(requested_ipv6);
timeout = discover_timeout;
packet_num++;
continue;
}
leasefail:
+ change_listen_mode(LISTEN_NONE);
d6_run_script_no_option("leasefail");
#if BB_MMU /* -b is not supported on NOMMU */
if (opt & OPT_b) { /* background if no lease */
@@ -1425,14 +1420,14 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
retval = 1;
goto ret;
}
- /* wait before trying again */
+ /* Wait before trying again */
timeout = tryagain_timeout;
packet_num = 0;
continue;
case REQUESTING:
if (!discover_retries || packet_num < discover_retries) {
/* send multicast select packet */
- send_d6_select(xid);
+ send_d6_select();
timeout = discover_timeout;
packet_num++;
continue;
@@ -1441,22 +1436,20 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
* "discover...select...discover..." loops
* were seen in the wild. Treat them similarly
* to "no response to discover" case */
- change_listen_mode(LISTEN_RAW);
client_data.state = INIT_SELECTING;
goto leasefail;
case BOUND:
/* 1/2 lease passed, enter renewing state */
client_data.state = RENEWING;
client_data.first_secs = 0; /* make secs field count from 0 */
- change_listen_mode(LISTEN_KERNEL);
+ got_SIGUSR1:
log1s("entering renew state");
+ change_listen_mode(LISTEN_KERNEL);
/* fall right through */
- case RENEW_REQUESTED: /* manual (SIGUSR1) renew */
- case_RENEW_REQUESTED:
+ case RENEW_REQUESTED: /* in manual (SIGUSR1) renew */
case RENEWING:
- if (packet_num < 3) {
- packet_num++;
- /* send an unicast renew request */
+ if (packet_num == 0) {
+ /* Send an unicast renew request */
/* Sometimes observed to fail (EADDRNOTAVAIL) to bind
* a new UDP socket for sending inside send_renew.
* I hazard to guess existing listening socket
@@ -1466,31 +1459,40 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
* into INIT_SELECTING state.
*/
if (opt & OPT_l)
- send_d6_info_request(xid);
+ send_d6_info_request();
else
- send_d6_renew(xid, &srv6_buf, requested_ipv6);
+ send_d6_renew(&srv6_buf, requested_ipv6);
timeout = discover_timeout;
- /* ^^^ used to be = lease_remaining / 2 - WAY too long */
+ packet_num++;
continue;
+ } /* else: we had sent one packet, but got no reply */
+ log1s("no response to renew");
+ if (lease_remaining > 30) {
+ /* Some lease time remains, try to renew later */
+ change_listen_mode(LISTEN_NONE);
+ goto BOUND_for_half_lease;
}
- /* Timed out, enter rebinding state */
- log1s("entering rebinding state");
+ /* Enter rebinding state */
client_data.state = REBINDING;
- /* fall right through */
- case REBINDING:
+ log1s("entering rebinding state");
/* Switch to bcast receive */
change_listen_mode(LISTEN_RAW);
+ packet_num = 0;
+ /* fall right through */
+ case REBINDING:
/* Lease is *really* about to run out,
* try to find DHCP server using broadcast */
- if (lease_remaining > 0) {
+ if (lease_remaining > 0 && packet_num < 3) {
if (opt & OPT_l)
- send_d6_info_request(xid);
+ send_d6_info_request();
else /* send a broadcast renew request */
- send_d6_renew(xid, /*server_ipv6:*/ NULL, requested_ipv6);
+ send_d6_renew(/*server_ipv6:*/ NULL, requested_ipv6);
timeout = discover_timeout;
+ packet_num++;
continue;
}
/* Timed out, enter init state */
+ change_listen_mode(LISTEN_NONE);
bb_simple_info_msg("lease lost, entering init state");
d6_run_script_no_option("deconfig");
client_data.state = INIT_SELECTING;
@@ -1500,7 +1502,9 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
continue;
/* case RELEASED: */
}
- /* yah, I know, *you* say it would never happen */
+ /* RELEASED state (when we got SIGUSR2) ends up here.
+ * (wait for SIGUSR1 to re-init, or for TERM, etc)
+ */
timeout = INT_MAX;
continue; /* back to main loop */
} /* if poll timed out */
@@ -1510,33 +1514,43 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
/* Is it a signal? */
switch (udhcp_sp_read()) {
case SIGUSR1:
+ if (client_data.state <= REQUESTING)
+ /* Initial negotiations in progress, do not disturb */
+ break;
+ if (client_data.state == REBINDING)
+ /* Do not go back from rebind to renew state */
+ break;
+
+ if (lease_remaining > 30) /* if renew fails, do not go back to BOUND */
+ lease_remaining = 30;
client_data.first_secs = 0; /* make secs field count from 0 */
- bb_simple_info_msg("performing DHCP renew");
+ packet_num = 0;
switch (client_data.state) {
- /* Try to renew/rebind */
case BOUND:
case RENEWING:
- case REBINDING:
+ /* Try to renew/rebind */
change_listen_mode(LISTEN_KERNEL);
client_data.state = RENEW_REQUESTED;
- goto case_RENEW_REQUESTED;
+ goto got_SIGUSR1;
- /* Start things over */
- case RENEW_REQUESTED: /* two or more SIGUSR1 received */
+ case RENEW_REQUESTED:
+ /* Two SIGUSR1 received, start things over */
+ change_listen_mode(LISTEN_NONE);
d6_run_script_no_option("deconfig");
- /* case REQUESTING: break; */
- /* case RELEASED: break; */
- /* case INIT_SELECTING: break; */
+
+ default:
+ /* case RELEASED: */
+ /* Wake from SIGUSR2-induced deconfigured state */
+ change_listen_mode(LISTEN_NONE);
}
- change_listen_mode(LISTEN_RAW);
client_data.state = INIT_SELECTING;
- packet_num = 0;
/* Kill any timeouts, user wants this to hurry along */
timeout = 0;
continue;
case SIGUSR2:
perform_d6_release(&srv6_buf, requested_ipv6);
+ /* ^^^ switches to LISTEN_NONE */
timeout = INT_MAX;
continue;
case SIGTERM:
@@ -1567,9 +1581,9 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
packet_end = (uint8_t*)&packet + len;
}
- if ((packet.d6_xid32 & htonl(0x00ffffff)) != xid) {
+ if ((packet.d6_xid32 & htonl(0x00ffffff)) != client_data.xid) {
log1("xid %x (our is %x)%s",
- (unsigned)(packet.d6_xid32 & htonl(0x00ffffff)), (unsigned)xid,
+ (unsigned)(packet.d6_xid32 & htonl(0x00ffffff)), (unsigned)client_data.xid,
", ignoring packet"
);
continue;
@@ -1594,6 +1608,8 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
unsigned address_timeout;
unsigned prefix_timeout;
type_is_ok:
+ change_listen_mode(LISTEN_NONE);
+
address_timeout = 0;
prefix_timeout = 0;
option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE);
@@ -1604,7 +1620,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
packet_end, "nak");
if (client_data.state != REQUESTING)
d6_run_script_no_option("deconfig");
- change_listen_mode(LISTEN_RAW);
sleep(3); /* avoid excessive network traffic */
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
@@ -1626,6 +1641,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
client6_data.server_id = option;
if (packet.d6_msg_type == D6_MSG_ADVERTISE) {
/* enter requesting state */
+ change_listen_mode(LISTEN_RAW);
client_data.state = REQUESTING;
timeout = 0;
packet_num = 0;
@@ -1809,12 +1825,9 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
start = monotonic_sec();
d6_run_script(packet.d6_options, packet_end,
(client_data.state == REQUESTING ? "bound" : "renew"));
- timeout = (unsigned)lease_remaining / 2;
- timeout -= (unsigned)monotonic_sec() - start;
- packet_num = 0;
-
- client_data.state = BOUND;
- change_listen_mode(LISTEN_NONE);
+ lease_remaining -= (unsigned)monotonic_sec() - start;
+ if (lease_remaining < 0)
+ lease_remaining = 0;
if (opt & OPT_q) { /* quit after lease */
goto ret0;
}
@@ -1827,6 +1840,11 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
opt = ((opt & ~OPT_b) | OPT_f);
}
#endif
+
+ BOUND_for_half_lease:
+ timeout = (unsigned)lease_remaining / 2;
+ client_data.state = BOUND;
+ packet_num = 0;
continue; /* back to main loop */
}
continue;
diff --git a/networking/udhcp/d6_packet.c b/networking/udhcp/d6_packet.c
index 172f8e1ab..c1949f6e3 100644
--- a/networking/udhcp/d6_packet.c
+++ b/networking/udhcp/d6_packet.c
@@ -44,7 +44,8 @@ int FAST_FUNC d6_recv_kernel_packet(struct in6_addr *peer_ipv6
bb_simple_info_msg("packet with bad magic, ignoring");
return -2;
}
- log1("received %s", "a packet");
+ log2("received %s", "a packet");
+ /* log2 because more informative msg for valid packets is printed later at log1 level */
d6_dump_packet(packet);
return bytes;
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 6666cbce6..331f13a8c 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -55,8 +55,7 @@ struct tpacket_auxdata {
#if ENABLE_LONG_OPTS
static const char udhcpc_longopts[] ALIGN1 =
"clientid-none\0" No_argument "C"
- "vendorclass\0" Required_argument "V"
- "hostname\0" Required_argument "H"
+ "vendorclass\0" Required_argument "V" //deprecated
"fqdn\0" Required_argument "F"
"interface\0" Required_argument "i"
"now\0" No_argument "n"
@@ -84,27 +83,25 @@ static const char udhcpc_longopts[] ALIGN1 =
enum {
OPT_C = 1 << 0,
OPT_V = 1 << 1,
- OPT_H = 1 << 2,
- OPT_h = 1 << 3,
- OPT_F = 1 << 4,
- OPT_i = 1 << 5,
- OPT_n = 1 << 6,
- OPT_p = 1 << 7,
- OPT_q = 1 << 8,
- OPT_R = 1 << 9,
- OPT_r = 1 << 10,
- OPT_s = 1 << 11,
- OPT_T = 1 << 12,
- OPT_t = 1 << 13,
- OPT_S = 1 << 14,
- OPT_A = 1 << 15,
- OPT_O = 1 << 16,
- OPT_o = 1 << 17,
- OPT_x = 1 << 18,
- OPT_f = 1 << 19,
- OPT_B = 1 << 20,
+ OPT_F = 1 << 2,
+ OPT_i = 1 << 3,
+ OPT_n = 1 << 4,
+ OPT_p = 1 << 5,
+ OPT_q = 1 << 6,
+ OPT_R = 1 << 7,
+ OPT_r = 1 << 8,
+ OPT_s = 1 << 9,
+ OPT_T = 1 << 10,
+ OPT_t = 1 << 11,
+ OPT_S = 1 << 12,
+ OPT_A = 1 << 13,
+ OPT_O = 1 << 14,
+ OPT_o = 1 << 15,
+ OPT_x = 1 << 16,
+ OPT_f = 1 << 17,
+ OPT_B = 1 << 18,
/* The rest has variable bit positions, need to be clever */
- OPTBIT_B = 20,
+ OPTBIT_B = 18,
USE_FOR_MMU( OPTBIT_b,)
IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
IF_FEATURE_UDHCP_PORT( OPTBIT_P,)
@@ -569,8 +566,8 @@ static void fill_envp(struct dhcp_packet *packet)
}
}
-/* Call a script with a par file and env vars */
-static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
+/* Call a script with env vars */
+static void d4_run_script(struct dhcp_packet *packet, const char *name)
{
char *argv[3];
@@ -588,6 +585,10 @@ static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
client_data.envp = NULL;
}
+static void d4_run_script_deconfig(void)
+{
+ d4_run_script(NULL, "deconfig");
+}
/*** Sending/receiving packets ***/
@@ -604,7 +605,7 @@ static void init_packet(struct dhcp_packet *packet, char type)
/* Fill in: op, htype, hlen, cookie fields; message type option: */
udhcp_init_header(packet, type);
- packet->xid = random_xid();
+ packet->xid = client_data.xid;
client_data.last_secs = monotonic_sec();
if (client_data.first_secs == 0)
@@ -613,8 +614,6 @@ static void init_packet(struct dhcp_packet *packet, char type)
packet->secs = (secs < 0xffff) ? htons(secs) : 0xffff;
memcpy(packet->chaddr, client_data.client_mac, 6);
- if (client_data.clientid)
- udhcp_add_binary_option(packet, client_data.clientid);
}
static void add_client_options(struct dhcp_packet *packet)
@@ -640,13 +639,6 @@ static void add_client_options(struct dhcp_packet *packet)
packet->options[end + OPT_DATA + len] = DHCP_END;
}
- if (client_data.vendorclass)
- udhcp_add_binary_option(packet, client_data.vendorclass);
- if (client_data.hostname)
- udhcp_add_binary_option(packet, client_data.hostname);
- if (client_data.fqdn)
- udhcp_add_binary_option(packet, client_data.fqdn);
-
/* Request broadcast replies if we have no IP addr */
if ((option_mask32 & OPT_B) && packet->ciaddr == 0)
packet->flags |= htons(BROADCAST_FLAG);
@@ -671,6 +663,24 @@ static void add_client_options(struct dhcp_packet *packet)
// ...add (DHCP_VENDOR, "udhcp "BB_VER) opt...
}
+static void add_serverid_and_clientid_options(struct dhcp_packet *packet, uint32_t server)
+{
+ struct option_set *ci;
+
+ udhcp_add_simple_option(packet, DHCP_SERVER_ID, server);
+
+ /* RFC 2131 section 2:
+ * If the client uses a 'client identifier' in one message,
+ * it MUST use that same identifier in all subsequent messages.
+ * section 3.1.6:
+ * If the client used a 'client identifier' when it obtained the lease,
+ * it MUST use the same 'client identifier' in the DHCPRELEASE message.
+ */
+ ci = udhcp_find_option(client_data.options, DHCP_CLIENT_ID);
+ if (ci)
+ udhcp_add_binary_option(packet, ci->data);
+}
+
/* RFC 2131
* 4.4.4 Use of broadcast and unicast
*
@@ -709,22 +719,19 @@ static int bcast_or_ucast(struct dhcp_packet *packet, uint32_t ciaddr, uint32_t
/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_discover(uint32_t xid, uint32_t requested)
+static NOINLINE int send_discover(uint32_t requested)
{
struct dhcp_packet packet;
/* Fill in: op, htype, hlen, cookie, chaddr fields,
- * random xid field (we override it below),
- * client-id option (unless -C), message type option:
+ * xid field, message type option:
*/
init_packet(&packet, DHCPDISCOVER);
- packet.xid = xid;
if (requested)
udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
/* Add options: maxsize,
- * optionally: hostname, fqdn, vendorclass,
* "param req" option according to -O, options specified with -x
*/
add_client_options(&packet);
@@ -738,7 +745,7 @@ static NOINLINE int send_discover(uint32_t xid, uint32_t requested)
* "The client _broadcasts_ a DHCPREQUEST message..."
*/
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requested)
+static NOINLINE int send_select(uint32_t server, uint32_t requested)
{
struct dhcp_packet packet;
struct in_addr temp_addr;
@@ -757,19 +764,16 @@ static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requeste
* include that list in all subsequent messages.
*/
/* Fill in: op, htype, hlen, cookie, chaddr fields,
- * random xid field (we override it below),
- * client-id option (unless -C), message type option:
+ * xid field, message type option:
*/
init_packet(&packet, DHCPREQUEST);
- packet.xid = xid;
udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
/* Add options: maxsize,
- * optionally: hostname, fqdn, vendorclass,
- * "param req" option according to -O, and options specified with -x
+ * "param req" option according to -O, options specified with -x
*/
add_client_options(&packet);
@@ -785,7 +789,7 @@ static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requeste
/* Unicast or broadcast a DHCP renew message */
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
+static NOINLINE int send_renew(uint32_t server, uint32_t ciaddr)
{
struct dhcp_packet packet;
@@ -804,17 +808,14 @@ static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
* replying to the client.
*/
/* Fill in: op, htype, hlen, cookie, chaddr fields,
- * random xid field (we override it below),
- * client-id option (unless -C), message type option:
+ * xid field, message type option:
*/
init_packet(&packet, DHCPREQUEST);
- packet.xid = xid;
packet.ciaddr = ciaddr;
/* Add options: maxsize,
- * optionally: hostname, fqdn, vendorclass,
- * "param req" option according to -O, and options specified with -x
+ * "param req" option according to -O, options specified with -x
*/
add_client_options(&packet);
@@ -832,27 +833,19 @@ static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
#if ENABLE_FEATURE_UDHCPC_ARPING
/* Broadcast a DHCP decline message */
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_decline(/*uint32_t xid,*/ uint32_t server, uint32_t requested)
+static NOINLINE int send_decline(uint32_t server, uint32_t requested)
{
struct dhcp_packet packet;
/* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
- * client-id option (unless -C), message type option:
+ * message type option:
*/
init_packet(&packet, DHCPDECLINE);
-#if 0
- /* RFC 2131 says DHCPDECLINE's xid is randomly selected by client,
- * but in case the server is buggy and wants DHCPDECLINE's xid
- * to match the xid which started entire handshake,
- * we use the same xid we used in initial DHCPDISCOVER:
- */
- packet.xid = xid;
-#endif
/* DHCPDECLINE uses "requested ip", not ciaddr, to store offered IP */
udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
- udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
+ add_serverid_and_clientid_options(&packet, server);
bb_simple_info_msg("broadcasting decline");
return raw_bcast_from_client_data_ifindex(&packet, INADDR_ANY);
@@ -867,14 +860,14 @@ int send_release(uint32_t server, uint32_t ciaddr)
struct dhcp_packet packet;
/* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
- * client-id option (unless -C), message type option:
+ * message type option:
*/
init_packet(&packet, DHCPRELEASE);
/* DHCPRELEASE uses ciaddr, not "requested ip", to store IP being released */
packet.ciaddr = ciaddr;
- udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
+ add_serverid_and_clientid_options(&packet, server);
bb_info_msg("sending %s", "release");
/* Note: normally we unicast here since "server" is not zero.
@@ -886,7 +879,7 @@ int send_release(uint32_t server, uint32_t ciaddr)
/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */
/* NOINLINE: limit stack usage in caller */
-static NOINLINE int udhcp_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd)
+static NOINLINE int d4_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd)
{
int bytes;
struct ip_udp_dhcp_packet packet;
@@ -979,11 +972,12 @@ static NOINLINE int udhcp_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd)
skip_udp_sum_check:
if (packet.data.cookie != htonl(DHCP_MAGIC)) {
- bb_simple_info_msg("packet with bad magic, ignoring");
+ log1s("packet with bad magic, ignoring");
return -2;
}
- log1("received %s", "a packet");
+ log2("received %s", "a packet");
+ /* log2 because more informative msg for valid packets is printed later at log1 level */
udhcp_dump_packet(&packet.data);
bytes -= sizeof(packet.ip) + sizeof(packet.udp);
@@ -1096,8 +1090,6 @@ static int udhcp_raw_socket(int ifindex)
log1s("can't set PACKET_AUXDATA on raw socket");
}
- log1s("created raw socket");
-
return fd;
}
@@ -1121,23 +1113,26 @@ static void change_listen_mode(int new_mode)
/* else LISTEN_NONE: client_data.sockfd stays closed */
}
-static void perform_release(uint32_t server_addr, uint32_t requested_ip)
+static void perform_release(uint32_t server_id, uint32_t requested_ip)
{
char buffer[sizeof("255.255.255.255")];
struct in_addr temp_addr;
+ change_listen_mode(LISTEN_NONE);
+
/* send release packet */
if (client_data.state == BOUND
|| client_data.state == RENEWING
|| client_data.state == REBINDING
|| client_data.state == RENEW_REQUESTED
) {
- temp_addr.s_addr = server_addr;
+ temp_addr.s_addr = server_id;
strcpy(buffer, inet_ntoa(temp_addr));
temp_addr.s_addr = requested_ip;
bb_info_msg("unicasting a release of %s to %s",
inet_ntoa(temp_addr), buffer);
- send_release(server_addr, requested_ip); /* unicast */
+ client_data.xid = random_xid(); //TODO: can omit?
+ send_release(server_id, requested_ip); /* unicast */
}
bb_simple_info_msg("entering released state");
/*
@@ -1146,23 +1141,10 @@ static void perform_release(uint32_t server_addr, uint32_t requested_ip)
* Users requested to be notified in all cases, even if not in one
* of the states above.
*/
- udhcp_run_script(NULL, "deconfig");
-
- change_listen_mode(LISTEN_NONE);
+ d4_run_script_deconfig();
client_data.state = RELEASED;
}
-static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
-{
- uint8_t *storage;
- int len = strnlen(str, 255);
- storage = xzalloc(len + extra + OPT_DATA);
- storage[OPT_CODE] = code;
- storage[OPT_LEN] = len + extra;
- memcpy(storage + extra + OPT_DATA, str, len);
- return storage;
-}
-
#if BB_MMU
static void client_background(void)
{
@@ -1179,7 +1161,7 @@ static void client_background(void)
//usage:# define IF_UDHCP_VERBOSE(...)
//usage:#endif
//usage:#define udhcpc_trivial_usage
-//usage: "[-fbq"IF_UDHCP_VERBOSE("v")"RB]"IF_FEATURE_UDHCPC_ARPING(" [-a[MSEC]]")" [-t N] [-T SEC] [-A SEC/-n]\n"
+//usage: "[-fbq"IF_UDHCP_VERBOSE("v")"RB]"IF_FEATURE_UDHCPC_ARPING(" [-a[MSEC]]")" [-t N] [-T SEC] [-A SEC|-n]\n"
//usage: " [-i IFACE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-s PROG] [-p PIDFILE]\n"
//usage: " [-oC] [-r IP] [-V VENDOR] [-F NAME] [-x OPT:VAL]... [-O OPT]..."
//usage:#define udhcpc_full_usage "\n"
@@ -1227,18 +1209,17 @@ int udhcpc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int udhcpc_main(int argc UNUSED_PARAM, char **argv)
{
uint8_t *message;
- const char *str_V, *str_h, *str_F, *str_r;
+ const char *str_V, *str_F, *str_r;
IF_FEATURE_UDHCPC_ARPING(const char *str_a = "2000";)
IF_FEATURE_UDHCP_PORT(char *str_P;)
- void *clientid_mac_ptr;
+ uint8_t *clientid_mac_ptr;
llist_t *list_O = NULL;
llist_t *list_x = NULL;
int tryagain_timeout = 20;
int discover_timeout = 3;
int discover_retries = 3;
- uint32_t server_addr = server_addr; /* for compiler */
+ uint32_t server_id = server_id; /* for compiler */
uint32_t requested_ip = 0;
- uint32_t xid = xid; /* for compiler */
int packet_num;
int timeout; /* must be signed */
int lease_remaining; /* must be signed */
@@ -1263,14 +1244,14 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
/* Parse command line */
opt = getopt32long(argv, "^"
/* O,x: list; -T,-t,-A take numeric param */
- "CV:H:h:F:i:np:qRr:s:T:+t:+SA:+O:*ox:*fB"
+ "CV:F:i:np:qRr:s:T:+t:+SA:+O:*ox:*fB"
USE_FOR_MMU("b")
IF_FEATURE_UDHCPC_ARPING("a::")
IF_FEATURE_UDHCP_PORT("P:")
"v"
"\0" IF_UDHCP_VERBOSE("vv") /* -v is a counter */
, udhcpc_longopts
- , &str_V, &str_h, &str_h, &str_F
+ , &str_V, &str_F
, &client_data.interface, &client_data.pidfile /* i,p */
, &str_r /* r */
, &client_data.script /* s */
@@ -1281,14 +1262,14 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
IF_FEATURE_UDHCP_PORT(, &str_P)
IF_UDHCP_VERBOSE(, &dhcp_verbose)
);
- if (opt & (OPT_h|OPT_H)) {
- //msg added 2011-11
- bb_simple_error_msg("option -h NAME is deprecated, use -x hostname:NAME");
- client_data.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0);
- }
if (opt & OPT_F) {
+ char *p;
+ unsigned len;
/* FQDN option format: [0x51][len][flags][0][0]<fqdn> */
- client_data.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3);
+ len = strlen(str_F);
+ p = udhcp_insert_new_option(
+ &client_data.options, DHCP_FQDN,
+ len + 3, /*dhcp6:*/ 0);
/* Flag bits: 0000NEOS
* S: 1 = Client requests server to update A RR in DNS as well as PTR
* O: 1 = Server indicates to client that DNS has been updated regardless
@@ -1297,12 +1278,14 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* N: 1 = Client requests server to not update DNS (S must be 0 then)
* Two [0] bytes which follow are deprecated and must be 0.
*/
- client_data.fqdn[OPT_DATA + 0] = 0x1;
- /*client_data.fqdn[OPT_DATA + 1] = 0; - xzalloc did it */
- /*client_data.fqdn[OPT_DATA + 2] = 0; */
+ p[OPT_DATA + 0] = 0x1;
+ /*p[OPT_DATA + 1] = 0; - xzalloc did it */
+ /*p[OPT_DATA + 2] = 0; */
+ memcpy(p + OPT_DATA + 3, str_F, len); /* do not store NUL byte */
}
if (opt & OPT_r)
- requested_ip = inet_addr(str_r);
+ if (!inet_aton(str_r, (void*)&requested_ip))
+ bb_show_usage();
#if ENABLE_FEATURE_UDHCP_PORT
if (opt & OPT_P) {
CLIENT_PORT = xatou16(str_P);
@@ -1335,7 +1318,29 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
);
free(optstr);
}
+ if (str_V[0] != '\0') {
+ char *p;
+ unsigned len = strnlen(str_V, 254);
+ p = udhcp_insert_new_option(
+ &client_data.options, DHCP_VENDOR,
+ len, /*dhcp6:*/ 0);
+ memcpy(p + OPT_DATA, str_V, len); /* do not store NUL byte */
+ }
+ clientid_mac_ptr = NULL;
+ if (!(opt & OPT_C) && !udhcp_find_option(client_data.options, DHCP_CLIENT_ID)) {
+ /* not suppressed and not set, create default client ID */
+ clientid_mac_ptr = udhcp_insert_new_option(
+ &client_data.options, DHCP_CLIENT_ID,
+ 1 + 6, /*dhcp6:*/ 0);
+ clientid_mac_ptr[OPT_DATA] = 1; /* type: ethernet */
+ clientid_mac_ptr += OPT_DATA + 1; /* skip option code, len, ethernet */
+ }
+
+ /* Not really necessary (we redo it on every iteration)
+ * but allows early (before daemonization) detection
+ * of bad interface name.
+ */
if (udhcp_read_interface(client_data.interface,
&client_data.ifindex,
NULL,
@@ -1344,24 +1349,6 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
return 1;
}
- clientid_mac_ptr = NULL;
- if (!(opt & OPT_C) && !udhcp_find_option(client_data.options, DHCP_CLIENT_ID)) {
- /* not suppressed and not set, set the default client ID */
- client_data.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7);
- client_data.clientid[OPT_DATA] = 1; /* type: ethernet */
- clientid_mac_ptr = client_data.clientid + OPT_DATA+1;
- memcpy(clientid_mac_ptr, client_data.client_mac, 6);
- }
- if (str_V[0] != '\0') {
- // can drop -V, str_V, client_data.vendorclass,
- // but need to add "vendor" to the list of recognized
- // string opts for this to work;
- // and need to tweak add_client_options() too...
- // ...so the question is, should we?
- //bb_error_msg("option -V VENDOR is deprecated, use -x vendor:VENDOR");
- client_data.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0);
- }
-
#if !BB_MMU
/* on NOMMU reexec (i.e., background) early */
if (!(opt & OPT_f)) {
@@ -1382,8 +1369,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
srand(monotonic_us());
client_data.state = INIT_SELECTING;
- udhcp_run_script(NULL, "deconfig");
- change_listen_mode(LISTEN_RAW);
+ d4_run_script_deconfig();
packet_num = 0;
timeout = 0;
lease_remaining = 0;
@@ -1417,14 +1403,17 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
log1("waiting %u seconds", timeout);
diff = (unsigned)monotonic_sec();
retval = poll(pfds, 2, timeout * 1000);
+ diff = (unsigned)monotonic_sec() - diff;
+ lease_remaining -= diff;
+ if (lease_remaining < 0)
+ lease_remaining = 0;
+ timeout -= diff;
+ if (timeout < 0)
+ timeout = 0;
+
if (retval < 0) {
/* EINTR? A signal was caught, don't panic */
if (errno == EINTR) {
- diff = (unsigned)monotonic_sec() - diff;
- lease_remaining -= diff;
- if (lease_remaining < 0)
- lease_remaining = 0;
- timeout -= diff;
continue;
}
/* Else: an error occurred, panic! */
@@ -1454,16 +1443,19 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
switch (client_data.state) {
case INIT_SELECTING:
if (!discover_retries || packet_num < discover_retries) {
- if (packet_num == 0)
- xid = random_xid();
+ if (packet_num == 0) {
+ change_listen_mode(LISTEN_RAW);
+ client_data.xid = random_xid();
+ }
/* broadcast */
- send_discover(xid, requested_ip);
+ send_discover(requested_ip);
timeout = discover_timeout;
packet_num++;
continue;
}
leasefail:
- udhcp_run_script(NULL, "leasefail");
+ change_listen_mode(LISTEN_NONE);
+ d4_run_script(NULL, "leasefail");
#if BB_MMU /* -b is not supported on NOMMU */
if (opt & OPT_b) { /* background if no lease */
bb_simple_info_msg("no lease, forking to background");
@@ -1483,14 +1475,14 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
retval = 1;
goto ret;
}
- /* wait before trying again */
+ /* Wait before trying again */
timeout = tryagain_timeout;
packet_num = 0;
continue;
case REQUESTING:
if (packet_num < 3) {
/* send broadcast select packet */
- send_select(xid, server_addr, requested_ip);
+ send_select(server_id, requested_ip);
timeout = discover_timeout;
packet_num++;
continue;
@@ -1499,22 +1491,20 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* "discover...select...discover..." loops
* were seen in the wild. Treat them similarly
* to "no response to discover" case */
- change_listen_mode(LISTEN_RAW);
client_data.state = INIT_SELECTING;
goto leasefail;
case BOUND:
/* 1/2 lease passed, enter renewing state */
client_data.state = RENEWING;
client_data.first_secs = 0; /* make secs field count from 0 */
- change_listen_mode(LISTEN_KERNEL);
+ got_SIGUSR1:
log1s("entering renew state");
+ change_listen_mode(LISTEN_KERNEL);
/* fall right through */
- case RENEW_REQUESTED: /* manual (SIGUSR1) renew */
- case_RENEW_REQUESTED:
+ case RENEW_REQUESTED: /* in manual (SIGUSR1) renew */
case RENEWING:
- if (packet_num < 3) {
- packet_num++;
- /* send an unicast renew request */
+ if (packet_num == 0) {
+ /* Send an unicast renew request */
/* Sometimes observed to fail (EADDRNOTAVAIL) to bind
* a new UDP socket for sending inside send_renew.
* I hazard to guess existing listening socket
@@ -1523,9 +1513,9 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* Anyway, it does recover by eventually failing through
* into INIT_SELECTING state.
*/
- if (send_renew(xid, server_addr, requested_ip) >= 0) {
+ if (send_renew(server_id, requested_ip) >= 0) {
timeout = discover_timeout;
- /* ^^^ used to be = lease_remaining / 2 - WAY too long */
+ packet_num++;
continue;
}
/* else: error sending.
@@ -1533,27 +1523,34 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* which gave us bogus server ID 1.1.1.1
* which wasn't reachable (and probably did not exist).
*/
+ } /* else: we had sent one packet, but got no reply */
+ log1s("no response to renew");
+ if (lease_remaining > 30) {
+ /* Some lease time remains, try to renew later */
+ change_listen_mode(LISTEN_NONE);
+ goto BOUND_for_half_lease;
}
-//TODO: if 3 renew's failed (no reply) but remaining lease is large,
-//it might make sense to make a large pause (~1 hour?) and try later?
- /* Timed out or error, enter rebinding state */
- log1s("entering rebinding state");
+ /* Enter rebinding state */
client_data.state = REBINDING;
- /* fall right through */
- case REBINDING:
+ log1s("entering rebinding state");
/* Switch to bcast receive */
change_listen_mode(LISTEN_RAW);
+ packet_num = 0;
+ /* fall right through */
+ case REBINDING:
/* Lease is *really* about to run out,
* try to find DHCP server using broadcast */
- if (lease_remaining > 0) {
+ if (lease_remaining > 0 && packet_num < 3) {
/* send a broadcast renew request */
- send_renew(xid, 0 /*INADDR_ANY*/, requested_ip);
+ send_renew(0 /*INADDR_ANY*/, requested_ip);
timeout = discover_timeout;
+ packet_num++;
continue;
}
/* Timed out, enter init state */
+ change_listen_mode(LISTEN_NONE);
bb_simple_info_msg("lease lost, entering init state");
- udhcp_run_script(NULL, "deconfig");
+ d4_run_script_deconfig();
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
timeout = 0;
@@ -1561,7 +1558,9 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
continue;
/* case RELEASED: */
}
- /* yah, I know, *you* say it would never happen */
+ /* RELEASED state (when we got SIGUSR2) ends up here.
+ * (wait for SIGUSR1 to re-init, or for TERM, etc)
+ */
timeout = INT_MAX;
continue; /* back to main loop */
} /* if poll timed out */
@@ -1571,33 +1570,42 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
/* Is it a signal? */
switch (udhcp_sp_read()) {
case SIGUSR1:
+ if (client_data.state <= REQUESTING)
+ /* Initial negotiations in progress, do not disturb */
+ break;
+ if (client_data.state == REBINDING)
+ /* Do not go back from rebind to renew state */
+ break;
+
+ if (lease_remaining > 30) /* if renew fails, do not go back to BOUND */
+ lease_remaining = 30;
client_data.first_secs = 0; /* make secs field count from 0 */
- bb_simple_info_msg("performing DHCP renew");
+ packet_num = 0;
switch (client_data.state) {
- /* Try to renew/rebind */
case BOUND:
case RENEWING:
- case REBINDING:
- change_listen_mode(LISTEN_KERNEL);
+ /* Try to renew/rebind */
client_data.state = RENEW_REQUESTED;
- goto case_RENEW_REQUESTED;
-
- /* Start things over */
- case RENEW_REQUESTED: /* two or more SIGUSR1 received */
- udhcp_run_script(NULL, "deconfig");
- /* case REQUESTING: break; */
- /* case RELEASED: break; */
- /* case INIT_SELECTING: break; */
+ goto got_SIGUSR1;
+
+ case RENEW_REQUESTED:
+ /* Two SIGUSR1 received, start things over */
+ change_listen_mode(LISTEN_NONE);
+ d4_run_script_deconfig();
+
+ default:
+ /* case RELEASED: */
+ /* Wake from SIGUSR2-induced deconfigured state */
+ change_listen_mode(LISTEN_NONE);
}
- change_listen_mode(LISTEN_RAW);
client_data.state = INIT_SELECTING;
- packet_num = 0;
/* Kill any timeouts, user wants this to hurry along */
timeout = 0;
continue;
case SIGUSR2:
- perform_release(server_addr, requested_ip);
+ perform_release(server_id, requested_ip);
+ /* ^^^ switches to LISTEN_NONE */
timeout = INT_MAX;
continue;
case SIGTERM:
@@ -1616,7 +1624,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
if (client_data.listen_mode == LISTEN_KERNEL)
len = udhcp_recv_kernel_packet(&packet, client_data.sockfd);
else
- len = udhcp_recv_raw_packet(&packet, client_data.sockfd);
+ len = d4_recv_raw_packet(&packet, client_data.sockfd);
if (len == -1) {
/* Error is severe, reopen socket */
bb_error_msg("read error: "STRERROR_FMT", reopening socket" STRERROR_ERRNO);
@@ -1627,9 +1635,9 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
continue;
}
- if (packet.xid != xid) {
+ if (packet.xid != client_data.xid) {
log1("xid %x (our is %x)%s",
- (unsigned)packet.xid, (unsigned)xid,
+ (unsigned)packet.xid, (unsigned)client_data.xid,
", ignoring packet"
);
continue;
@@ -1640,13 +1648,13 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
|| memcmp(packet.chaddr, client_data.client_mac, 6) != 0
) {
//FIXME: need to also check that last 10 bytes are zero
- log1("chaddr does not match%s", ", ignoring packet"); // log2?
+ log1("chaddr does not match%s", ", ignoring packet");
continue;
}
message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
if (message == NULL) {
- bb_info_msg("no message type option%s", ", ignoring packet");
+ log1("no message type option%s", ", ignoring packet");
continue;
}
@@ -1654,6 +1662,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
case INIT_SELECTING:
/* Must be a DHCPOFFER */
if (*message == DHCPOFFER) {
+ struct in_addr temp_addr;
uint8_t *temp;
/* What exactly is server's IP? There are several values.
@@ -1680,16 +1689,17 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* They either supply DHCP_SERVER_ID of 0.0.0.0 or don't supply it at all.
* They say ISC DHCP client supports this case.
*/
- server_addr = 0;
+ server_id = 0;
temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
if (!temp) {
bb_simple_info_msg("no server ID, using 0.0.0.0");
} else {
/* it IS unaligned sometimes, don't "optimize" */
- move_from_unaligned32(server_addr, temp);
+ move_from_unaligned32(server_id, temp);
}
/*xid = packet.xid; - already is */
- requested_ip = packet.yiaddr;
+ temp_addr.s_addr = requested_ip = packet.yiaddr;
+ log1("received offer of %s", inet_ntoa(temp_addr));
/* enter requesting state */
client_data.state = REQUESTING;
@@ -1707,14 +1717,15 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
char server_str[sizeof("255.255.255.255")];
uint8_t *temp;
- temp_addr.s_addr = server_addr;
+ change_listen_mode(LISTEN_NONE);
+
+ temp_addr.s_addr = server_id;
strcpy(server_str, inet_ntoa(temp_addr));
temp_addr.s_addr = packet.yiaddr;
+ lease_remaining = 60 * 60;
temp = udhcp_get_option32(&packet, DHCP_LEASE_TIME);
- if (!temp) {
- lease_remaining = 60 * 60;
- } else {
+ if (temp) {
uint32_t lease;
/* it IS unaligned sometimes, don't "optimize" */
move_from_unaligned32(lease, temp);
@@ -1755,11 +1766,11 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
) {
bb_simple_info_msg("offered address is in use "
"(got ARP reply), declining");
- send_decline(/*xid,*/ server_addr, packet.yiaddr);
+ client_data.xid = random_xid(); //TODO: can omit?
+ send_decline(server_id, packet.yiaddr);
if (client_data.state != REQUESTING)
- udhcp_run_script(NULL, "deconfig");
- change_listen_mode(LISTEN_RAW);
+ d4_run_script_deconfig();
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
requested_ip = 0;
@@ -1769,17 +1780,12 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
}
}
#endif
-
/* enter bound state */
start = monotonic_sec();
- udhcp_run_script(&packet, client_data.state == REQUESTING ? "bound" : "renew");
- timeout = (unsigned)lease_remaining / 2;
-//TODO: why / 2?
- timeout -= (unsigned)monotonic_sec() - start;
- packet_num = 0;
-
- client_data.state = BOUND;
- change_listen_mode(LISTEN_NONE);
+ d4_run_script(&packet, client_data.state == REQUESTING ? "bound" : "renew");
+ lease_remaining -= (unsigned)monotonic_sec() - start;
+ if (lease_remaining < 0)
+ lease_remaining = 0;
if (opt & OPT_q) { /* quit after lease */
goto ret0;
}
@@ -1792,9 +1798,12 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
opt = ((opt & ~OPT_b) | OPT_f);
}
#endif
+ BOUND_for_half_lease:
+ timeout = (unsigned)lease_remaining / 2;
+ client_data.state = BOUND;
/* make future renew packets use different xid */
- /* xid = random_xid(); ...but why bother? */
-
+ /* client_data.xid = random_xid(); ...but why bother? */
+ packet_num = 0;
continue; /* back to main loop */
}
if (*message == DHCPNAK) {
@@ -1802,27 +1811,21 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
* "wrong" server can reply first, with a NAK.
* Do not interpret it as a NAK from "our" server.
*/
- if (server_addr != 0) {
- uint32_t svid;
- uint8_t *temp;
-
- temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
- if (!temp) {
- non_matching_svid:
- log1("received DHCP NAK with wrong"
- " server ID%s", ", ignoring packet");
- continue;
- }
+ uint32_t svid = 0; /* we treat no server id as 0.0.0.0 */
+ uint8_t *temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
+ if (temp)
move_from_unaligned32(svid, temp);
- if (svid != server_addr)
- goto non_matching_svid;
+ if (svid != server_id) {
+ log1("received DHCP NAK with wrong"
+ " server ID%s", ", ignoring packet");
+ continue;
}
/* return to init state */
+ change_listen_mode(LISTEN_NONE);
bb_info_msg("received %s", "DHCP NAK");
- udhcp_run_script(&packet, "nak");
+ d4_run_script(&packet, "nak");
if (client_data.state != REQUESTING)
- udhcp_run_script(NULL, "deconfig");
- change_listen_mode(LISTEN_RAW);
+ d4_run_script_deconfig();
sleep(3); /* avoid excessive network traffic */
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
@@ -1839,7 +1842,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
ret0:
if (opt & OPT_R) /* release on quit */
- perform_release(server_addr, requested_ip);
+ perform_release(server_id, requested_ip);
retval = 0;
ret:
/*if (client_data.pidfile) - remove_pidfile has its own check */
diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h
index 7ad01ea8f..19b054b32 100644
--- a/networking/udhcp/dhcpc.h
+++ b/networking/udhcp/dhcpc.h
@@ -11,16 +11,13 @@ struct client_data_t {
uint8_t client_mac[6]; /* Our mac address */
IF_FEATURE_UDHCP_PORT(uint16_t port;)
int ifindex; /* Index number of the interface to use */
+ uint32_t xid;
uint8_t opt_mask[256 / 8]; /* Bitmask of options to send (-O option) */
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TODO: DHCPv6 has 16-bit option numbers
const char *interface; /* The name of the interface to use */
char *pidfile; /* Optionally store the process ID */
const char *script; /* User script to run at dhcp events */
struct option_set *options; /* list of DHCP options to send to server */
- uint8_t *clientid; /* Optional client id to use */
- uint8_t *vendorclass; /* Optional vendor class-id to use */
- uint8_t *hostname; /* Optional hostname to use */
- uint8_t *fqdn; /* Optional fully qualified domain name to use */
llist_t *envp; /* list of DHCP options used for env vars */
unsigned first_secs;
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c
index 260130507..91f70970a 100644
--- a/networking/udhcp/dhcpd.c
+++ b/networking/udhcp/dhcpd.c
@@ -295,12 +295,11 @@ static uint32_t find_free_or_expired_nip(const uint8_t *safe_mac, unsigned arppi
uint32_t nip;
struct dyn_lease *lease;
- /* ie, 192.168.55.0 */
- if ((addr & 0xff) == 0)
- goto next_addr;
- /* ie, 192.168.55.255 */
- if ((addr & 0xff) == 0xff)
- goto next_addr;
+ /* (Addresses ending in .0 or .255 can legitimately be allocated
+ * in various situations, so _don't_ skip these. The user needs
+ * to choose start_ip and end_ip correctly for a particular
+ * network environment.) */
+
nip = htonl(addr);
/* skip our own address */
if (nip == server_data.server_nip)
diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c
index 4d8e005d4..78f580ce9 100644
--- a/networking/udhcp/packet.c
+++ b/networking/udhcp/packet.c
@@ -95,7 +95,8 @@ int FAST_FUNC udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd)
bb_simple_info_msg("packet with bad magic, ignoring");
return -2;
}
- log1("received %s", "a packet");
+ log2("received %s", "a packet");
+ /* log2 because more informative msg for valid packets is printed later at log1 level */
udhcp_dump_packet(packet);
return bytes;
diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c
index 65a1a8ead..35e10688b 100644
--- a/networking/udhcp/socket.c
+++ b/networking/udhcp/socket.c
@@ -82,7 +82,7 @@ int FAST_FUNC udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf)
struct sockaddr_in addr;
char *colon;
- log1("opening listen socket on *:%d %s", port, inf);
+ log2("opening listen socket on *:%d %s", port, inf);
fd = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt_reuseaddr(fd);
diff --git a/networking/vconfig.c b/networking/vconfig.c
index 4f1fbe280..7e805be9c 100644
--- a/networking/vconfig.c
+++ b/networking/vconfig.c
@@ -20,12 +20,12 @@
//usage: "COMMAND [OPTIONS]"
//usage:#define vconfig_full_usage "\n\n"
//usage: "Create and remove virtual ethernet devices\n"
-//usage: "\n add IFACE VLAN_ID"
-//usage: "\n rem VLAN_NAME"
-//usage: "\n set_flag IFACE 0|1 VLAN_QOS"
-//usage: "\n set_egress_map VLAN_NAME SKB_PRIO VLAN_QOS"
-//usage: "\n set_ingress_map VLAN_NAME SKB_PRIO VLAN_QOS"
-//usage: "\n set_name_type NAME_TYPE"
+//usage: "\n add IFACE VLAN_ID"
+//usage: "\n rem VLAN_NAME"
+//usage: "\n set_flag IFACE 0|1 VLAN_QOS"
+//usage: "\n set_egress_map VLAN_NAME SKB_PRIO VLAN_QOS"
+//usage: "\n set_ingress_map VLAN_NAME SKB_PRIO VLAN_QOS"
+//usage: "\n set_name_type NAME_TYPE"
#include "libbb.h"
#include <net/if.h>
diff --git a/procps/free.c b/procps/free.c
index 0adef501f..0b68e1b88 100644
--- a/procps/free.c
+++ b/procps/free.c
@@ -19,9 +19,9 @@
//kbuild:lib-$(CONFIG_FREE) += free.o
//usage:#define free_trivial_usage
-//usage: "" IF_DESKTOP("[-b/k/m/g]")
+//usage: "" IF_DESKTOP("[-bkmgh]")
//usage:#define free_full_usage "\n\n"
-//usage: "Display the amount of free and used system memory"
+//usage: "Display free and used memory"
//usage:
//usage:#define free_example_usage
//usage: "$ free\n"
@@ -29,6 +29,27 @@
//usage: " Mem: 257628 248724 8904 59644 93124\n"
//usage: " Swap: 128516 8404 120112\n"
//usage: "Total: 386144 257128 129016\n"
+//procps-ng 3.3.15:
+// -b, --bytes show output in bytes
+// --kilo show output in kilobytes
+// --mega show output in megabytes
+// --giga show output in gigabytes
+// --tera show output in terabytes
+// --peta show output in petabytes
+// -k, --kibi show output in kibibytes
+// -m, --mebi show output in mebibytes
+// -g, --gibi show output in gibibytes
+// --tebi show output in tebibytes
+// --pebi show output in pebibytes
+// -h, --human show human-readable output
+// --si use powers of 1000 not 1024
+// -l, --lohi show detailed low and high memory statistics
+// -t, --total show total for RAM + swap
+// -s N, --seconds N repeat printing every N seconds
+// -c N, --count N repeat printing N times, then exit
+// -w, --wide wide output
+//
+//NB: if we implement -s or -c, need to stop being NOFORK!
#include "libbb.h"
#ifdef __linux__
@@ -38,18 +59,22 @@
struct globals {
unsigned mem_unit;
#if ENABLE_DESKTOP
- uint8_t unit_steps;
-# define G_unit_steps g->unit_steps
+ unsigned unit;
+# define G_unit g->unit
#else
-# define G_unit_steps 10
+# define G_unit (1 << 10)
#endif
unsigned long cached_kb, available_kb, reclaimable_kb;
};
/* Because of NOFORK, "globals" are not in global data */
-static unsigned long long scale(struct globals *g, unsigned long d)
+static const char *scale(struct globals *g, unsigned long d)
{
- return ((unsigned long long)d * g->mem_unit) >> G_unit_steps;
+ /* Display (size * block_size) with one decimal digit.
+ * If display_unit == 0, show value no bigger than 1024 with suffix (K,M,G...),
+ * else divide by display_unit and do not use suffix.
+ * Returns "auto pointer" */
+ return make_human_readable_str(d, g->mem_unit, G_unit);
}
/* NOINLINE reduces main() stack usage, which makes code smaller (on x86 at least) */
@@ -88,20 +113,27 @@ int free_main(int argc UNUSED_PARAM, char **argv IF_NOT_DESKTOP(UNUSED_PARAM))
int seen_available;
#if ENABLE_DESKTOP
- G.unit_steps = 10;
+ G.unit = 1 << 10;
if (argv[1] && argv[1][0] == '-') {
switch (argv[1][1]) {
case 'b':
- G.unit_steps = 0;
+ G.unit = 1;
break;
case 'k': /* 2^10 */
- /* G.unit_steps = 10; - already is */
+ /* G.unit = 1 << 10; - already is */
break;
case 'm': /* 2^20 */
- G.unit_steps = 20;
+ G.unit = 1 << 20;
break;
case 'g': /* 2^30 */
- G.unit_steps = 30;
+ G.unit = 1 << 30;
+ break;
+// case 't':
+// -- WRONG, -t is not "terabytes" in procps-ng, it's --total
+// G.unit = 1 << 40;
+// break;
+ case 'h':
+ G.unit = 0; /* human readable */
break;
default:
bb_show_usage();
@@ -126,23 +158,13 @@ int free_main(int argc UNUSED_PARAM, char **argv IF_NOT_DESKTOP(UNUSED_PARAM))
cached += ((unsigned long long) G.reclaimable_kb * 1024) / G.mem_unit;
cached_plus_free = cached + info.freeram;
-/* In case (long long * G.mem_unit) can overflow, this can be used to reduce the chances */
-#if 0 //ENABLE_DESKTOP
- while (!(G.mem_unit & 1) && G.unit_steps != 0) {
- G.mem_unit >>= 1;
- G.unit_steps--;
- //bb_error_msg("mem_unit:%d unit_steps:%d", G.mem_unit, G.unit_steps);
- }
-#endif
-
-#define FIELDS_6 "%12llu %11llu %11llu %11llu %11llu %11llu\n"
-#define FIELDS_3 (FIELDS_6 + 6 + 7 + 7)
-#define FIELDS_2 (FIELDS_6 + 6 + 7 + 7 + 7)
-
- printf(FIELDS_6,
+ printf("%12s%12s%12s",
scale(&G, info.totalram), //total
scale(&G, info.totalram - cached_plus_free), //used
- scale(&G, info.freeram), //free
+ scale(&G, info.freeram) //free
+ );
+ /* using two printf's: only 4 auto strings are supported, we need 6 */
+ printf("%12s%12s%12s\n",
scale(&G, info.sharedram), //shared
scale(&G, cached), //buff/cache
scale(&G, available) //available
@@ -152,14 +174,14 @@ int free_main(int argc UNUSED_PARAM, char **argv IF_NOT_DESKTOP(UNUSED_PARAM))
* buffer cache as free memory. */
if (!seen_available) {
printf("-/+ buffers/cache: ");
- printf(FIELDS_2,
+ printf("%12s%12s%12s\n" + 4,
scale(&G, info.totalram - cached_plus_free), //used
scale(&G, cached_plus_free) //free
);
}
#if BB_MMU
printf("Swap: ");
- printf(FIELDS_3,
+ printf("%12s%12s%12s\n",
scale(&G, info.totalswap), //total
scale(&G, info.totalswap - info.freeswap), //used
scale(&G, info.freeswap) //free
diff --git a/procps/kill.c b/procps/kill.c
index 4a2eab613..8f10e21ab 100644
--- a/procps/kill.c
+++ b/procps/kill.c
@@ -59,7 +59,7 @@
//usage: "$ kill 252\n"
//usage:
//usage:#define killall_trivial_usage
-//usage: "[-l] [-q] [-SIG] PROCESS_NAME..."
+//usage: "[-lq] [-SIG] PROCESS_NAME..."
//usage:#define killall_full_usage "\n\n"
//usage: "Send a signal (default: TERM) to given processes\n"
//usage: "\n -l List all signal names and numbers"
diff --git a/procps/lsof.c b/procps/lsof.c
index 21ac85ed3..9cb8d066c 100644
--- a/procps/lsof.c
+++ b/procps/lsof.c
@@ -66,7 +66,7 @@ int lsof_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
safe_strncpy(name + baseofs, entry->d_name, 10);
if ((fdlink = xmalloc_readlink(name)) != NULL) {
- printf("%d\t%s\t%s\n", proc->pid, proc->exe, fdlink);
+ printf("%d\t%s\t%s\t%s\n", proc->pid, proc->exe, entry->d_name, fdlink);
free(fdlink);
}
}
diff --git a/procps/pgrep.c b/procps/pgrep.c
index 495e0ef9d..6d25c247e 100644
--- a/procps/pgrep.c
+++ b/procps/pgrep.c
@@ -44,17 +44,17 @@
//usage: "\n -P Match parent process ID"
//usage:
//usage:#define pkill_trivial_usage
-//usage: "[-l|-SIGNAL] [-fnovx] [-s SID|-P PPID|PATTERN]"
+//usage: "[-l|-SIGNAL] [-xfvno] [-s SID|-P PPID|PATTERN]"
//usage:#define pkill_full_usage "\n\n"
-//usage: "Send a signal to process(es) selected by regex PATTERN\n"
+//usage: "Send signal to processes selected by regex PATTERN\n"
//usage: "\n -l List all signals"
+//usage: "\n -x Match whole name (not substring)"
//usage: "\n -f Match against entire command line"
+//usage: "\n -s SID Match session ID (0 for current)"
+//usage: "\n -P PPID Match parent process ID"
+//usage: "\n -v Negate the match"
//usage: "\n -n Signal the newest process only"
//usage: "\n -o Signal the oldest process only"
-//usage: "\n -v Negate the match"
-//usage: "\n -x Match whole name (not substring)"
-//usage: "\n -s Match session ID (0 for current)"
-//usage: "\n -P Match parent process ID"
#include "libbb.h"
#include "xregex.h"
diff --git a/procps/sysctl.c b/procps/sysctl.c
index e16b119e9..40afa0c90 100644
--- a/procps/sysctl.c
+++ b/procps/sysctl.c
@@ -21,16 +21,16 @@
//kbuild:lib-$(CONFIG_BB_SYSCTL) += sysctl.o
//usage:#define sysctl_trivial_usage
-//usage: "-p [-enq] [FILE...] / [-enqaw] [KEY[=VALUE]]..."
+//usage: "[-enq] { -a | -p [FILE]... | [-w] [KEY[=VALUE]]... }"
//usage:#define sysctl_full_usage "\n\n"
//usage: "Show/set kernel parameters\n"
-//usage: "\n -p Set values from FILEs (default /etc/sysctl.conf)"
//usage: "\n -e Don't warn about unknown keys"
//usage: "\n -n Don't show key names"
//usage: "\n -q Quiet"
//usage: "\n -a Show all values"
/* Same as -a, no need to show it */
/* //usage: "\n -A Show all values in table form" */
+//usage: "\n -p Set values from FILEs (default /etc/sysctl.conf)"
//usage: "\n -w Set values"
//usage:
//usage:#define sysctl_example_usage
diff --git a/procps/top.c b/procps/top.c
index cadc4ecec..4cd545c69 100644
--- a/procps/top.c
+++ b/procps/top.c
@@ -1052,9 +1052,9 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval)
//usage: "[-b"IF_FEATURE_TOPMEM("m")IF_FEATURE_SHOW_THREADS("H")"]"
//usage: " [-n COUNT] [-d SECONDS]"
//usage:#define top_full_usage "\n\n"
-//usage: "Provide a view of process activity in real time."
+//usage: "Show a view of process activity in real time."
//usage: "\n""Read the status of all processes from /proc each SECONDS"
-//usage: "\n""and display a screenful of them."
+//usage: "\n""and show a screenful of them."
//usage: "\n"
//usage: IF_FEATURE_TOP_INTERACTIVE(
//usage: "Keys:"
diff --git a/procps/watch.c b/procps/watch.c
index 059eb1dda..1190b29df 100644
--- a/procps/watch.c
+++ b/procps/watch.c
@@ -22,7 +22,7 @@
//usage: "[-n SEC] [-t] PROG ARGS"
//usage:#define watch_full_usage "\n\n"
//usage: "Run PROG periodically\n"
-//usage: "\n -n SEC Loop period (default 2)"
+//usage: "\n -n SEC Period (default 2)"
//usage: "\n -t Don't print header"
//usage:
//usage:#define watch_example_usage
diff --git a/runit/runsv.c b/runit/runsv.c
index ecab8cdf5..a4b8af494 100644
--- a/runit/runsv.c
+++ b/runit/runsv.c
@@ -149,17 +149,18 @@ static void warn_cannot(const char *m)
warn2_cannot(m, "");
}
-static void s_child(int sig_no UNUSED_PARAM)
+/* SIGCHLD/TERM handler is reentrancy-safe because they are unmasked
+ * only over poll() call, not over memory allocations
+ * or printouts. Do not need to save/restore errno either,
+ * as poll() error is not checked there.
+ */
+static void s_chld_term(int sig_no)
{
+ if (sig_no == SIGTERM)
+ sigterm = 1;
write(selfpipe.wr, "", 1);
}
-static void s_term(int sig_no UNUSED_PARAM)
-{
- sigterm = 1;
- write(selfpipe.wr, "", 1); /* XXX */
-}
-
static int open_trunc_or_warn(const char *name)
{
/* Why O_NDELAY? */
@@ -380,11 +381,14 @@ static void startservice(struct svdir *s)
xdup2(logpipe.wr, 1);
}
}
- /* Non-ignored signals revert to SIG_DFL on exec anyway */
- /*bb_signals(0
- + (1 << SIGCHLD)
- + (1 << SIGTERM)
- , SIG_DFL);*/
+ /* Non-ignored signals revert to SIG_DFL on exec.
+ * But we can get signals BEFORE execl(), unlikely as that may be.
+ * SIGCHLD is safe (would merely write to selfpipe),
+ * but SIGTERM would set sigterm = 1 (with vfork, we affect parent).
+ * Avoid that.
+ */
+ /*signal(SIGCHLD, SIG_DFL);*/
+ signal(SIGTERM, SIG_DFL);
sig_unblock(SIGCHLD);
sig_unblock(SIGTERM);
execv(arg[0], (char**) arg);
@@ -511,9 +515,15 @@ int runsv_main(int argc UNUSED_PARAM, char **argv)
ndelay_on(selfpipe.wr);
sig_block(SIGCHLD);
- bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
sig_block(SIGTERM);
- bb_signals_recursive_norestart(1 << SIGTERM, s_term);
+ /* No particular reason why we don't set SA_RESTART
+ * (poll() wouldn't restart regardless of that flag),
+ * we just follow what runit-2.1.2 does:
+ */
+ bb_signals_norestart(0
+ + (1 << SIGCHLD)
+ + (1 << SIGTERM)
+ , s_chld_term);
xchdir(dir);
/* bss: svd[0].pid = 0; */
@@ -625,6 +635,7 @@ int runsv_main(int argc UNUSED_PARAM, char **argv)
sig_unblock(SIGTERM);
sig_unblock(SIGCHLD);
poll(x, 2 + haslog, 3600*1000);
+ /* NB: signal handlers can trash errno of poll() */
sig_block(SIGTERM);
sig_block(SIGCHLD);
diff --git a/runit/svlogd.c b/runit/svlogd.c
index 294e31aca..f7576f0fa 100644
--- a/runit/svlogd.c
+++ b/runit/svlogd.c
@@ -140,12 +140,12 @@ log message, you can use a pattern like this instead
//usage:#define svlogd_full_usage "\n\n"
//usage: "Read log data from stdin and write to rotated log files in DIRs"
//usage: "\n"
-//usage: "\n""-r C Replace non-printable characters with C"
-//usage: "\n""-R CHARS Also replace CHARS with C (default _)"
-//usage: "\n""-t Timestamp with @tai64n"
-//usage: "\n""-tt Timestamp with yyyy-mm-dd_hh:mm:ss.sssss"
-//usage: "\n""-ttt Timestamp with yyyy-mm-ddThh:mm:ss.sssss"
-//usage: "\n""-v Verbose"
+//usage: "\n"" -r C Replace non-printable characters with C"
+//usage: "\n"" -R CHARS Also replace CHARS with C (default _)"
+//usage: "\n"" -t Timestamp with @tai64n"
+//usage: "\n"" -tt Timestamp with yyyy-mm-dd_hh:mm:ss.sssss"
+//usage: "\n"" -ttt Timestamp with yyyy-mm-ddThh:mm:ss.sssss"
+//usage: "\n"" -v Verbose"
//usage: "\n"
//usage: "\n""DIR/config file modifies behavior:"
//usage: "\n""sSIZE - when to rotate logs (default 1000000, 0 disables)"
@@ -412,19 +412,32 @@ static void processorstart(struct logdir *ld)
int fd;
/* child */
- /* Non-ignored signals revert to SIG_DFL on exec anyway */
+ /* Non-ignored signals revert to SIG_DFL on exec anyway.
+ * But we can get signals BEFORE execl(), this is unlikely
+ * but wouldn't be good...
+ */
/*bb_signals(0
+ (1 << SIGTERM)
+ //+ (1 << SIGCHLD)
+ (1 << SIGALRM)
+ (1 << SIGHUP)
, SIG_DFL);*/
- sig_unblock(SIGTERM);
- sig_unblock(SIGALRM);
- sig_unblock(SIGHUP);
+ /* runit 2.1.2 does not unblock SIGCHLD, a bug? we do: */
+ sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
if (verbose)
bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
- fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
+
+ fd = open_or_warn(ld->fnsave, O_RDONLY|O_NDELAY);
+ /* Used to have xopen() above, but it causes infinite restarts of processor
+ * if file is gone - which can happen even because of _us_!
+ * Users report that if on reboot, time is reset to before existing
+ * logfiles creation time, rmoldest() deletes the newest logfile (!)
+ * and we end up here trying to open this now-deleted file.
+ */
+ if (fd < 0)
+ _exit(0); /* fake "success": do not run processor again */
+
xmove_fd(fd, 0);
ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
@@ -1098,10 +1111,10 @@ int svlogd_main(int argc, char **argv)
sigaddset(&blocked_sigset, SIGALRM);
sigaddset(&blocked_sigset, SIGHUP);
sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
- bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
- bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
- bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
- bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
+ bb_signals_norestart(1 << SIGTERM, sig_term_handler);
+ bb_signals_norestart(1 << SIGCHLD, sig_child_handler);
+ bb_signals_norestart(1 << SIGALRM, sig_alarm_handler);
+ bb_signals_norestart(1 << SIGHUP, sig_hangup_handler);
/* Without timestamps, we don't have to print each line
* separately, so we can look for _last_ newline, not first,
diff --git a/scripts/gen_build_files.sh b/scripts/gen_build_files.sh
index 92de681ac..8b5b15a1b 100755
--- a/scripts/gen_build_files.sh
+++ b/scripts/gen_build_files.sh
@@ -4,6 +4,8 @@
# but users complain that many sed implementations
# are misinterpreting --.
+export LC_ALL=C
+
test $# -ge 2 || { echo "Syntax: $0 SRCTREE OBJTREE"; exit 1; }
# cd to objtree
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
index 9976011a9..249a3195e 100644
--- a/scripts/kconfig/confdata.c
+++ b/scripts/kconfig/confdata.c
@@ -342,6 +342,8 @@ int conf_write(const char *name)
time_t now;
int use_timestamp = 1;
char *env;
+ char *source_date_epoch;
+ struct tm *build_time;
dirname[0] = 0;
if (name && name[0]) {
@@ -378,7 +380,16 @@ int conf_write(const char *name)
}
sym = sym_lookup("KERNELVERSION", 0);
sym_calc_value(sym);
- time(&now);
+
+ source_date_epoch = getenv("SOURCE_DATE_EPOCH");
+ if (source_date_epoch && *source_date_epoch) {
+ now = strtoull(source_date_epoch, NULL, 10);
+ build_time = gmtime(&now);
+ } else {
+ time(&now);
+ build_time = localtime(&now);
+ }
+
env = getenv("KCONFIG_NOTIMESTAMP");
if (env && *env)
use_timestamp = 0;
@@ -398,14 +409,14 @@ int conf_write(const char *name)
if (use_timestamp) {
size_t ret = \
strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP "
- "\"%Y-%m-%d %H:%M:%S %Z\"\n", localtime(&now));
+ "\"%Y-%m-%d %H:%M:%S %Z\"\n", build_time);
/* if user has Factory timezone or some other odd install, the
* %Z above will overflow the string leaving us with undefined
* results ... so let's try again without the timezone.
*/
if (ret == 0)
strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP "
- "\"%Y-%m-%d %H:%M:%S\"\n", localtime(&now));
+ "\"%Y-%m-%d %H:%M:%S\"\n", build_time);
} else { /* bbox */
strcpy(buf, "#define AUTOCONF_TIMESTAMP \"\"\n");
}
diff --git a/shell/ash.c b/shell/ash.c
index 6a16833b1..b5947147a 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -229,6 +229,14 @@
#define BASH_READ_D ENABLE_ASH_BASH_COMPAT
#define IF_BASH_READ_D IF_ASH_BASH_COMPAT
#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT
+/* <(...) and >(...) */
+#if HAVE_DEV_FD
+# define BASH_PROCESS_SUBST ENABLE_ASH_BASH_COMPAT
+# define IF_BASH_PROCESS_SUBST IF_ASH_BASH_COMPAT
+#else
+# define BASH_PROCESS_SUBST 0
+# define IF_BASH_PROCESS_SUBST(...)
+#endif
#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
/* Bionic at least up to version 24 has no glob() */
@@ -804,6 +812,12 @@ out2str(const char *p)
#define CTLENDARI ((unsigned char)'\207')
#define CTLQUOTEMARK ((unsigned char)'\210')
#define CTL_LAST CTLQUOTEMARK
+#if BASH_PROCESS_SUBST
+# define CTLTOPROC ((unsigned char)'\211')
+# define CTLFROMPROC ((unsigned char)'\212')
+# undef CTL_LAST
+# define CTL_LAST CTLFROMPROC
+#endif
/* variable substitution byte (follows CTLVAR) */
#define VSTYPE 0x0f /* type of variable substitution */
@@ -1075,6 +1089,10 @@ trace_puts_quoted(char *s)
case CTLESC: c = 'e'; goto backslash;
case CTLVAR: c = 'v'; goto backslash;
case CTLBACKQ: c = 'q'; goto backslash;
+#if BASH_PROCESS_SUBST
+ case CTLTOPROC: c = 'p'; goto backslash;
+ case CTLFROMPROC: c = 'P'; goto backslash;
+#endif
backslash:
putc('\\', tracefile);
putc(c, tracefile);
@@ -1236,8 +1254,17 @@ sharg(union node *arg, FILE *fp)
case CTLENDVAR:
putc('}', fp);
break;
+#if BASH_PROCESS_SUBST
+ case CTLTOPROC:
+ putc('>', fp);
+ goto backq;
+ case CTLFROMPROC:
+ putc('<', fp);
+ goto backq;
+#endif
case CTLBACKQ:
putc('$', fp);
+ IF_BASH_PROCESS_SUBST(backq:)
putc('(', fp);
shtree(bqlist->n, -1, NULL, fp);
putc(')', fp);
@@ -3234,8 +3261,13 @@ static const uint8_t syntax_index_table[] ALIGN1 = {
/* 134 CTLARI */ CCTL_CCTL_CCTL_CCTL,
/* 135 CTLENDARI */ CCTL_CCTL_CCTL_CCTL,
/* 136 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL,
+#if BASH_PROCESS_SUBST
+ /* 137 CTLTOPROC */ CCTL_CCTL_CCTL_CCTL,
+ /* 138 CTLFROMPROC */ CCTL_CCTL_CCTL_CCTL,
+#else
/* 137 */ CWORD_CWORD_CWORD_CWORD,
/* 138 */ CWORD_CWORD_CWORD_CWORD,
+#endif
/* 139 */ CWORD_CWORD_CWORD_CWORD,
/* 140 */ CWORD_CWORD_CWORD_CWORD,
/* 141 */ CWORD_CWORD_CWORD_CWORD,
@@ -4849,9 +4881,24 @@ cmdputs(const char *s)
quoted >>= 1;
subtype = 0;
goto dostr;
+#if BASH_PROCESS_SUBST
+ case CTLBACKQ:
+ c = '$';
+ str = "(...)";
+ break;
+ case CTLTOPROC:
+ c = '>';
+ str = "(...)";
+ break;
+ case CTLFROMPROC:
+ c = '<';
+ str = "(...)";
+ break;
+#else
case CTLBACKQ:
str = "$(...)";
goto dostr;
+#endif
#if ENABLE_FEATURE_SH_MATH
case CTLARI:
str = "$((";
@@ -5891,6 +5938,21 @@ redirectsafe(union node *redir, int flags)
return err;
}
+#if BASH_PROCESS_SUBST
+static void
+pushfd(int fd)
+{
+ struct redirtab *sv;
+
+ sv = ckzalloc(sizeof(*sv) + sizeof(sv->two_fd[0]));
+ sv->pair_count = 1;
+ sv->two_fd[0].orig_fd = fd;
+ sv->two_fd[0].moved_to = CLOSED;
+ sv->next = redirlist;
+ redirlist = sv;
+}
+#endif
+
static struct redirtab*
pushredir(union node *redir)
{
@@ -6529,10 +6591,20 @@ evaltreenr(union node *n, int flags)
}
static void FAST_FUNC
-evalbackcmd(union node *n, struct backcmd *result)
+evalbackcmd(union node *n, struct backcmd *result
+ IF_BASH_PROCESS_SUBST(, int ctl))
{
int pip[2];
struct job *jp;
+#if BASH_PROCESS_SUBST
+ /* determine end of pipe used by parent (ip) and child (ic) */
+ const int ip = (ctl == CTLTOPROC);
+ const int ic = !(ctl == CTLTOPROC);
+#else
+ const int ctl = CTLBACKQ;
+ const int ip = 0;
+ const int ic = 1;
+#endif
result->fd = -1;
result->buf = NULL;
@@ -6544,15 +6616,17 @@ evalbackcmd(union node *n, struct backcmd *result)
if (pipe(pip) < 0)
ash_msg_and_raise_perror("can't create pipe");
- jp = makejob(/*n,*/ 1);
- if (forkshell(jp, n, FORK_NOJOB) == 0) {
+ /* process substitution uses NULL job/node, like openhere() */
+ jp = (ctl == CTLBACKQ) ? makejob(/*n,*/ 1) : NULL;
+ if (forkshell(jp, (ctl == CTLBACKQ) ? n : NULL, FORK_NOJOB) == 0) {
/* child */
FORCE_INT_ON;
- close(pip[0]);
- if (pip[1] != 1) {
- /*close(1);*/
- dup2_or_raise(pip[1], 1);
- close(pip[1]);
+ close(pip[ip]);
+ /* ic is index of child end of pipe *and* fd to connect it to */
+ if (pip[ic] != ic) {
+ /*close(ic);*/
+ dup2_or_raise(pip[ic], ic);
+ close(pip[ic]);
}
/* TODO: eflag clearing makes the following not abort:
* ash -c 'set -e; z=$(false;echo foo); echo $z'
@@ -6568,8 +6642,18 @@ evalbackcmd(union node *n, struct backcmd *result)
/* NOTREACHED */
}
/* parent */
- close(pip[1]);
- result->fd = pip[0];
+#if BASH_PROCESS_SUBST
+ if (ctl != CTLBACKQ) {
+ int fd = fcntl(pip[ip], F_DUPFD, 64);
+ if (fd > 0) {
+ close(pip[ip]);
+ pip[ip] = fd;
+ }
+ pushfd(pip[ip]);
+ }
+#endif
+ close(pip[ic]);
+ result->fd = pip[ip];
result->jp = jp;
out:
@@ -6581,8 +6665,11 @@ evalbackcmd(union node *n, struct backcmd *result)
* Expand stuff in backwards quotes.
*/
static void
-expbackq(union node *cmd, int flag)
+expbackq(union node *cmd, int flag IF_BASH_PROCESS_SUBST(, int ctl))
{
+#if !BASH_PROCESS_SUBST
+ const int ctl = CTLBACKQ;
+#endif
struct backcmd in;
int i;
char buf[128];
@@ -6597,9 +6684,15 @@ expbackq(union node *cmd, int flag)
INT_OFF;
startloc = expdest - (char *)stackblock();
pushstackmark(&smark, startloc);
- evalbackcmd(cmd, &in);
+ evalbackcmd(cmd, &in IF_BASH_PROCESS_SUBST(, ctl));
popstackmark(&smark);
+ if (ctl != CTLBACKQ) {
+ sprintf(buf, DEV_FD_PREFIX"%d", in.fd);
+ strtodest(buf, BASESYNTAX);
+ goto done;
+ }
+
p = in.buf;
i = in.nleft;
if (i == 0)
@@ -6621,6 +6714,7 @@ expbackq(union node *cmd, int flag)
close(in.fd);
back_exitstatus = waitforjob(in.jp);
}
+ done:
INT_ON;
/* Eat all trailing newlines */
@@ -6708,6 +6802,10 @@ argstr(char *p, int flag)
CTLESC,
CTLVAR,
CTLBACKQ,
+#if BASH_PROCESS_SUBST
+ CTLTOPROC,
+ CTLFROMPROC,
+#endif
#if ENABLE_FEATURE_SH_MATH
CTLARI,
CTLENDARI,
@@ -6807,8 +6905,12 @@ argstr(char *p, int flag)
p = evalvar(p, flag | inquotes);
TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
goto start;
+#if BASH_PROCESS_SUBST
+ case CTLTOPROC:
+ case CTLFROMPROC:
+#endif
case CTLBACKQ:
- expbackq(argbackq->n, flag | inquotes);
+ expbackq(argbackq->n, flag | inquotes IF_BASH_PROCESS_SUBST(, c));
goto start;
#if ENABLE_FEATURE_SH_MATH
case CTLARI:
@@ -6868,7 +6970,8 @@ scanright(char *startp, char *rmesc, char *rmescend,
* Logic:
* loc starts at NUL at the end of startp, loc2 starts at the end of rmesc,
* and on each iteration they go back two/one char until they reach the beginning.
- * We try to find a match in "raw_value_of_v", "raw_value_of_", "raw_value_of" etc.
+ * We try to match "raw_value_of_v", "raw_value_of_", "raw_value_of" etc.
+ * If one of these matches, return pointer past last matched char in startp.
*/
/* TODO: document in what other circumstances we are called. */
@@ -7156,6 +7259,7 @@ subevalvar(char *start, char *str, int strloc,
#if BASH_PATTERN_SUBST
workloc = expdest - (char *)stackblock();
if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
+ size_t no_meta_len;
int len;
char *idx, *end;
@@ -7173,16 +7277,41 @@ subevalvar(char *start, char *str, int strloc,
if (str[0] == '\0')
goto out1;
+ no_meta_len = (ENABLE_ASH_OPTIMIZE_FOR_SIZE || strpbrk(str, "*?[\\")) ? 0 : strlen(str);
len = 0;
idx = startp;
end = str - 1;
while (idx <= end) {
try_to_match:
- loc = scanright(idx, rmesc, rmescend, str, quotes, 1);
+ if (no_meta_len == 0) {
+ /* pattern has meta chars, have to glob; or ENABLE_ASH_OPTIMIZE_FOR_SIZE */
+ loc = scanright(idx, rmesc, rmescend, str, quotes, /*match_at_start:*/ 1);
+ } else {
+ /* Testcase for very slow replace (performs about 22k replaces):
+ * x=::::::::::::::::::::::
+ * x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;echo ${#x}
+ * echo "${x//:/|}"
+ */
+ if (strncmp(rmesc, str, no_meta_len) != 0)
+ goto no_match;
+ loc = idx;
+ if (!quotes) {
+ loc += no_meta_len;
+ } else {
+ size_t n = no_meta_len;
+ do {
+ if ((unsigned char)*loc == CTLESC)
+ loc++;
+ loc++;
+ } while (--n != 0);
+ }
+ }
//bb_error_msg("scanright('%s'):'%s'", str, loc);
if (!loc) {
+ char *restart_detect;
+ no_match:
/* No match, advance */
- char *restart_detect = stackblock();
+ restart_detect = stackblock();
skip_matching:
if (idx >= end)
break;
@@ -12198,8 +12327,9 @@ realeofmark(const char *eofmark)
#define CHECKEND() {goto checkend; checkend_return:;}
#define PARSEREDIR() {goto parseredir; parseredir_return:;}
#define PARSESUB() {goto parsesub; parsesub_return:;}
-#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
-#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
+#define PARSEBACKQOLD() {style = OLD; goto parsebackq; parsebackq_oldreturn:;}
+#define PARSEBACKQNEW() {style = NEW; goto parsebackq; parsebackq_newreturn:;}
+#define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;}
#define PARSEARITH() {goto parsearith; parsearith_return:;}
static int
readtoken1(int c, int syntax, char *eofmark, int striptabs)
@@ -12210,7 +12340,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
size_t len;
struct nodelist *bqlist;
smallint quotef;
- smallint oldstyle;
+ smallint style;
+ enum { OLD, NEW, PSUB };
+#define oldstyle (style == OLD)
smallint pssyntax; /* we are expanding a prompt string */
IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
/* syntax stack */
@@ -12392,6 +12524,15 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
pungetc();
}
#endif
+#if BASH_PROCESS_SUBST
+ if (c == '<' || c == '>') {
+ if (pgetc() == '(') {
+ PARSEPROCSUB();
+ break;
+ }
+ pungetc();
+ }
+#endif
goto endword; /* exit outer loop */
}
IF_ASH_ALIAS(if (c != PEOA))
@@ -12637,7 +12778,7 @@ parsesub: {
do {
STPUTC(c, out);
c = pgetc_eatbnl();
- } while (!subtype && isdigit(c));
+ } while ((subtype == 0 || subtype == VSLENGTH) && isdigit(c));
} else if (c != '}') {
/* $[{[#]]<specialchar>[}] */
int cc = c;
@@ -12667,11 +12808,6 @@ parsesub: {
} else
goto badsub;
- if (c != '}' && subtype == VSLENGTH) {
- /* ${#VAR didn't end with } */
- goto badsub;
- }
-
if (subtype == 0) {
static const char types[] ALIGN1 = "}-+?=";
/* ${VAR...} but not $VAR or ${#VAR} */
@@ -12728,6 +12864,8 @@ parsesub: {
#endif
}
} else {
+ if (subtype == VSLENGTH && c != '}')
+ subtype = 0;
badsub:
pungetc();
}
@@ -12876,9 +13014,18 @@ parsebackq: {
memcpy(out, str, savelen);
STADJUST(savelen, out);
}
- USTPUTC(CTLBACKQ, out);
+#if BASH_PROCESS_SUBST
+ if (style == PSUB)
+ USTPUTC(c == '<' ? CTLFROMPROC : CTLTOPROC, out);
+ else
+#endif
+ USTPUTC(CTLBACKQ, out);
if (oldstyle)
goto parsebackq_oldreturn;
+#if BASH_PROCESS_SUBST
+ else if (style == PSUB)
+ goto parsebackq_psreturn;
+#endif
goto parsebackq_newreturn;
}
@@ -13330,6 +13477,9 @@ cmdloop(int top)
if (doing_jobctl)
showjobs(SHOW_CHANGED|SHOW_STDERR);
#endif
+#if BASH_PROCESS_SUBST
+ unwindredir(NULL);
+#endif
inter = 0;
if (iflag && top) {
inter++;
@@ -14035,6 +14185,10 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
}
}
+ if (!ENABLE_ASH_BASH_COMPAT && !argptr) {
+ bb_simple_error_msg("read: need variable name");
+ return 1;
+ }
params.argv = argptr;
params.setvar = setvar0;
params.ifs = bltinlookup("IFS"); /* can be NULL */
@@ -14268,7 +14422,7 @@ init(void)
//usage:#define ash_trivial_usage
-//usage: "[-il] [-/+Cabefmnuvx] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
+//usage: "[-il] [-|+Cabefmnuvx] [-|+o OPT]... [-c 'SCRIPT' [ARG0 ARGS] | FILE [ARGS] | -s [ARGS]]"
//////// comes from ^^^^^^^^^^optletters
//usage:#define ash_full_usage "\n\n"
//usage: "Unix shell interpreter"
diff --git a/shell/ash_remove_unnecessary_code_in_backquote_expansion.patch b/shell/ash_remove_unnecessary_code_in_backquote_expansion.patch
new file mode 100644
index 000000000..06067dde0
--- /dev/null
+++ b/shell/ash_remove_unnecessary_code_in_backquote_expansion.patch
@@ -0,0 +1,135 @@
+From: Herbert Xu <herbert@xxxxxxxxxxxxxxxxxxx>
+Date: Thu, 19 Apr 2018 18:16:12 +0800
+
+> ash originally had support for omitting the fork when expanding a
+> builtin in backquotes. dash has gradually been removing this support,
+> most recently in commit 66b614e29038e31745c4a5d296f64f8d64f5c377
+> ("[EVAL] Remove unused EV_BACKCMD flag").
+>
+> Some traces still remain, however. Remove:
+>
+> - the buf and nleft elements of the backcmd structure;
+> - a misleading comment regarding handling of builtins.
+>
+> Signed-off-by: Ron Yorston <rmy@xxxxxxxxxxxx>
+
+Unfortunately we may need this at some point in the future due
+to changes in POSIX. So let's keep it around for now until we
+get things such as `jobs -p` to work.
+
+*************************************
+
+From: Ron Yorston <rmy@xxxxxxxxxxxx>
+Date: Thu, 19 Apr 2018 17:18:47 +0100
+
+>Unfortunately we may need this at some point in the future due
+>to changes in POSIX. So let's keep it around for now until we
+>get things such as `jobs -p` to work.
+
+As you wish.
+
+Something even more trivial I noticed later: the TRACE at the end of
+expbackq incorrectly refers to the function as evalbackq.
+
+*************************************
+
+Date: Tue, 10 Apr 2018 13:23:35 +0100
+From: Ron Yorston <rmy@pobox.com>
+To: busybox@busybox.net
+Subject: [PATCH] ash: remove unnecessary code in backquote expansion
+
+Some traces remain of ash's ancient support for omitting the fork when
+expanding a builtin command in backquotes.
+
+Remove:
+
+- the buf and nleft elements of the backcmd structure;
+- a misleading comment regarding handling of builtins.
+
+I've submitted a similar patch to dash.
+
+Signed-off-by: Ron Yorston <rmy@pobox.com>
+---
+ shell/ash.c | 37 +++++++++----------------------------
+ 1 file changed, 9 insertions(+), 28 deletions(-)
+
+diff --git a/shell/ash.c b/shell/ash.c
+index 45c747dbc..6f1458722 100644
+--- a/shell/ash.c
++++ b/shell/ash.c
+@@ -6356,15 +6356,12 @@ exptilde(char *startp, char *p, int flags)
+ }
+
+ /*
+- * Execute a command inside back quotes. If it's a builtin command, we
+- * want to save its output in a block obtained from malloc. Otherwise
+- * we fork off a subprocess and get the output of the command via a pipe.
+- * Should be called with interrupts off.
++ * Execute a command inside back quotes. We fork off a subprocess and
++ * get the output of the command via a pipe. Should be called with
++ * interrupts off.
+ */
+ struct backcmd { /* result of evalbackcmd */
+ int fd; /* file descriptor to read from */
+- int nleft; /* number of chars in buffer */
+- char *buf; /* buffer */
+ struct job *jp; /* job structure for command */
+ };
+
+@@ -6394,8 +6391,6 @@ evalbackcmd(union node *n, struct backcmd *result)
+ struct job *jp;
+
+ result->fd = -1;
+- result->buf = NULL;
+- result->nleft = 0;
+ result->jp = NULL;
+ if (n == NULL) {
+ goto out;
+@@ -6432,8 +6427,7 @@ evalbackcmd(union node *n, struct backcmd *result)
+ result->jp = jp;
+
+ out:
+- TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+- result->fd, result->buf, result->nleft, result->jp));
++ TRACE(("evalbackcmd done: fd=%d jp=0x%x\n", result->fd, result->jp));
+ }
+
+ /*
+@@ -6445,7 +6439,6 @@ expbackq(union node *cmd, int flag)
+ struct backcmd in;
+ int i;
+ char buf[128];
+- char *p;
+ char *dest;
+ int startloc;
+ int syntax = flag & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
+@@ -6457,24 +6450,12 @@ expbackq(union node *cmd, int flag)
+ evalbackcmd(cmd, &in);
+ popstackmark(&smark);
+
+- p = in.buf;
+- i = in.nleft;
+- if (i == 0)
+- goto read;
+- for (;;) {
+- memtodest(p, i, syntax, flag & QUOTES_ESC);
+- read:
+- if (in.fd < 0)
+- break;
+- i = nonblock_immune_read(in.fd, buf, sizeof(buf));
+- TRACE(("expbackq: read returns %d\n", i));
+- if (i <= 0)
+- break;
+- p = buf;
+- }
+-
+- free(in.buf);
+ if (in.fd >= 0) {
++ while ((i = nonblock_immune_read(in.fd, buf, sizeof(buf))) > 0) {
++ TRACE(("expbackq: read returns %d\n", i));
++ memtodest(buf, i, syntax, flag & QUOTES_ESC);
++ }
++
+ close(in.fd);
+ back_exitstatus = waitforjob(in.jp);
+ }
diff --git a/shell/ash_test/ash-misc/control_char3.right b/shell/ash_test/ash-misc/control_char3.right
new file mode 100644
index 000000000..283e02cbb
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char3.right
@@ -0,0 +1 @@
+SHELL: line 1: : not found
diff --git a/shell/ash_test/ash-misc/control_char3.tests b/shell/ash_test/ash-misc/control_char3.tests
new file mode 100755
index 000000000..4359db3f3
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char3.tests
@@ -0,0 +1,2 @@
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+$THIS_SH -c '\' SHELL
diff --git a/shell/ash_test/ash-misc/control_char4.right b/shell/ash_test/ash-misc/control_char4.right
new file mode 100644
index 000000000..2bf18e684
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char4.right
@@ -0,0 +1 @@
+SHELL: line 1: -: not found
diff --git a/shell/ash_test/ash-misc/control_char4.tests b/shell/ash_test/ash-misc/control_char4.tests
new file mode 100755
index 000000000..48010f154
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char4.tests
@@ -0,0 +1,2 @@
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+$THIS_SH -c '"-"' SHELL
diff --git a/shell/ash_test/ash-parsing/bkslash_newline4.right b/shell/ash_test/ash-parsing/bkslash_newline4.right
new file mode 100644
index 000000000..2110716d1
--- /dev/null
+++ b/shell/ash_test/ash-parsing/bkslash_newline4.right
@@ -0,0 +1,4 @@
+1:1
+22:22
+3:3
+Ok:0
diff --git a/shell/ash_test/ash-parsing/bkslash_newline4.tests b/shell/ash_test/ash-parsing/bkslash_newline4.tests
new file mode 100755
index 000000000..c8f5037c4
--- /dev/null
+++ b/shell/ash_test/ash-parsing/bkslash_newline4.tests
@@ -0,0 +1,14 @@
+set -- 1 22 333
+echo 1:$\
+1
+echo 22:$\
+{\
+2\
+}
+echo 3:$\
+{\
+#\
+3\
+}
+echo Ok:$\
+?
diff --git a/shell/ash_test/ash-psubst/bash_procsub.right b/shell/ash_test/ash-psubst/bash_procsub.right
new file mode 100644
index 000000000..aa16a96be
--- /dev/null
+++ b/shell/ash_test/ash-psubst/bash_procsub.right
@@ -0,0 +1,9 @@
+hello 1
+hello 2
+hello 3
+<(echo "hello 0")
+hello 4
+HI THERE
+hello error
+hello error
+hello stderr
diff --git a/shell/ash_test/ash-psubst/bash_procsub.tests b/shell/ash_test/ash-psubst/bash_procsub.tests
new file mode 100755
index 000000000..63b836782
--- /dev/null
+++ b/shell/ash_test/ash-psubst/bash_procsub.tests
@@ -0,0 +1,33 @@
+# simplest case
+cat <(echo "hello 1")
+
+# can have more than one
+cat <(echo "hello 2") <(echo "hello 3")
+
+# doesn't work in quotes
+echo "<(echo \"hello 0\")"
+
+# process substitution can be nested inside command substitution
+echo $(cat <(echo "hello 4"))
+
+# example from http://wiki.bash-hackers.org/syntax/expansion/proc_subst
+# process substitutions can be passed to a function as parameters or
+# variables
+f() {
+ cat "$1" >"$x"
+}
+x=>(tr '[:lower:]' '[:upper:]') f <(echo 'hi there')
+
+# process substitution can be combined with redirection on exec
+rm -f err
+# save stderr
+exec 4>&2
+# copy stderr to a file
+exec 2> >(tee err)
+echo "hello error" >&2
+sync
+# restore stderr
+exec 2>&4
+cat err
+rm -f err
+echo "hello stderr" >&2
diff --git a/shell/ash_test/ash-psubst/falsetick.right b/shell/ash_test/ash-psubst/falsetick.right
new file mode 100644
index 000000000..0335254a8
--- /dev/null
+++ b/shell/ash_test/ash-psubst/falsetick.right
@@ -0,0 +1,24 @@
+0
+0
+0
+0
+2
+2
+2
+2
+./falsetick.tests: line 12: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 13: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 14: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 15: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 16: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 17: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 18: can't create /does/not/exist: nonexistent directory
+1
+./falsetick.tests: line 19: can't create /does/not/exist: nonexistent directory
+1
diff --git a/shell/ash_test/ash-psubst/falsetick.tests b/shell/ash_test/ash-psubst/falsetick.tests
new file mode 100755
index 000000000..d2b93695e
--- /dev/null
+++ b/shell/ash_test/ash-psubst/falsetick.tests
@@ -0,0 +1,19 @@
+# Exitcode 0 (`` has no exitcode, but assignment has):
+true; a=``; echo $?
+false; a=``; echo $?
+true; a=$(); echo $?
+false; a=$(); echo $?
+# Exitcode 2 (`cmd` expansion sets exitcode after assignment set it to 0):
+true; a=`exit 2`; echo $?
+false; a=`exit 2`; echo $?
+true; a=$(exit 2); echo $?
+false; a=$(exit 2); echo $?
+# Exitcode 1 (redirect sets exitcode to 1 on error after them):
+true; a=`` >/does/not/exist; echo $?
+false; a=`` >/does/not/exist; echo $?
+true; a=$() >/does/not/exist; echo $?
+false; a=$() >/does/not/exist; echo $?
+true; a=`exit 2` >/does/not/exist; echo $?
+false; a=`exit 2` >/does/not/exist; echo $?
+true; a=$(exit 2) >/does/not/exist; echo $?
+false; a=$(exit 2) >/does/not/exist; echo $?
diff --git a/shell/ash_test/ash-psubst/falsetick2.right b/shell/ash_test/ash-psubst/falsetick2.right
new file mode 100644
index 000000000..670f560f1
--- /dev/null
+++ b/shell/ash_test/ash-psubst/falsetick2.right
@@ -0,0 +1 @@
+Two:2 v:[]
diff --git a/shell/ash_test/ash-psubst/falsetick2.tests b/shell/ash_test/ash-psubst/falsetick2.tests
new file mode 100755
index 000000000..cfbd1a5de
--- /dev/null
+++ b/shell/ash_test/ash-psubst/falsetick2.tests
@@ -0,0 +1,3 @@
+v=v
+v=`exit 2` `false`
+echo Two:$? v:"[$v]"
diff --git a/shell/ash_test/ash-vars/var6.right b/shell/ash_test/ash-vars/var6.right
new file mode 100644
index 000000000..b37417fa1
--- /dev/null
+++ b/shell/ash_test/ash-vars/var6.right
@@ -0,0 +1,2 @@
+SHELL: line 1: syntax error: bad substitution
+SHELL: line 1: syntax error: bad substitution
diff --git a/shell/ash_test/ash-vars/var6.tests b/shell/ash_test/ash-vars/var6.tests
new file mode 100755
index 000000000..626e60ee1
--- /dev/null
+++ b/shell/ash_test/ash-vars/var6.tests
@@ -0,0 +1,5 @@
+# reject invalid vars
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+"$THIS_SH" -c 'echo ${1q}' SHELL
+"$THIS_SH" -c 'echo ${&}' SHELL
+#"$THIS_SH" -c 'echo ${$}' SHELL -- this is valid as it's the same as $$
diff --git a/shell/ash_test/ash-vars/var_nested1.right b/shell/ash_test/ash-vars/var_nested1.right
new file mode 100644
index 000000000..f4bc5f4b6
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_nested1.right
@@ -0,0 +1,3 @@
+Expected:AB Actual:AB
+Expected:Ab Actual:Ab
+Expected:ab Actual:ab
diff --git a/shell/ash_test/ash-vars/var_nested1.tests b/shell/ash_test/ash-vars/var_nested1.tests
new file mode 100755
index 000000000..59e4a14fa
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_nested1.tests
@@ -0,0 +1,16 @@
+f() { a=A; b=B; }
+
+a=a
+b=b
+f
+echo Expected:AB Actual:$a$b
+
+a=a
+b=b
+b= f
+echo Expected:Ab Actual:$a$b
+
+a=a
+b=b
+a= b= f
+echo Expected:ab Actual:$a$b
diff --git a/shell/ash_test/ash-vars/var_nested2.right b/shell/ash_test/ash-vars/var_nested2.right
new file mode 100644
index 000000000..c930d971c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_nested2.right
@@ -0,0 +1 @@
+aB
diff --git a/shell/ash_test/ash-vars/var_nested2.tests b/shell/ash_test/ash-vars/var_nested2.tests
new file mode 100755
index 000000000..e8865861e
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_nested2.tests
@@ -0,0 +1,2 @@
+# the bug was easier to trigger in one-liner form
+a=a; b=b; f() { a=A; b=B; }; a= f; echo $a$b
diff --git a/shell/hush.c b/shell/hush.c
index 144ad3edd..c970d9097 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -339,7 +339,7 @@
* therefore we don't show them either.
*/
//usage:#define hush_trivial_usage
-//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
+//usage: "[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE [ARGS] | -s [ARGS]]"
//usage:#define hush_full_usage "\n\n"
//usage: "Unix shell interpreter"
@@ -384,6 +384,7 @@
#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT
#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
+#define BASH_DOLLAR_SQUOTE ENABLE_HUSH_BASH_COMPAT
#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
#define BASH_EPOCH_VARS ENABLE_HUSH_BASH_COMPAT
#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
@@ -3696,9 +3697,10 @@ static void debug_print_tree(struct pipe *pi, int lvl)
pin = 0;
while (pi) {
- fdprintf(2, "%*spipe %d %sres_word=%s followup=%d %s\n",
+ fdprintf(2, "%*spipe %d #cmds:%d %sres_word=%s followup=%d %s\n",
lvl*2, "",
pin,
+ pi->num_cmds,
(IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
RES[pi->res_word],
pi->followup, PIPE[pi->followup]
@@ -3841,6 +3843,9 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
#endif
/* Replace all pipes in ctx with one newly created */
ctx->list_head = ctx->pipe = pi;
+ /* for cases like "cmd && &", do not be tricked by last command
+ * being null - the entire {...} & is NOT null! */
+ not_null = 1;
} else {
no_conv:
ctx->pipe->followup = type;
@@ -4251,7 +4256,7 @@ static int done_word(struct parse_context *ctx)
|| endofname(command->argv[0])[0] != '\0'
) {
/* bash says just "not a valid identifier" */
- syntax_error("not a valid identifier in for");
+ syntax_error("bad variable name in for");
return 1;
}
/* Force FOR to have just one word (variable name) */
@@ -4915,6 +4920,101 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
}
#endif /* ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS */
+#if BASH_DOLLAR_SQUOTE
+/* Return code: 1 for "found and parsed", 0 for "seen something else" */
+# if BB_MMU
+#define parse_dollar_squote(as_string, dest, input) \
+ parse_dollar_squote(dest, input)
+#define as_string NULL
+# endif
+static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_str *input)
+{
+ int start;
+ int ch = i_peek_and_eat_bkslash_nl(input); /* first character after the $ */
+ debug_printf_parse("parse_dollar_squote entered: ch='%c'\n", ch);
+ if (ch != '\'')
+ return 0;
+
+ dest->has_quoted_part = 1;
+ start = dest->length;
+
+ ch = i_getch(input); /* eat ' */
+ nommu_addchr(as_string, ch);
+ while (1) {
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ return 0;
+ }
+ if (ch == '\'')
+ break;
+ if (ch == SPECIAL_VAR_SYMBOL) {
+ /* Convert raw ^C to corresponding special variable reference */
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(dest, SPECIAL_VAR_QUOTED_SVS);
+ /* will addchr() another SPECIAL_VAR_SYMBOL (see after the if() block) */
+ } else if (ch == '\\') {
+ static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567";
+
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ if (strchr(C_escapes, ch)) {
+ char buf[4];
+ char *p = buf;
+ int cnt = 2;
+
+ buf[0] = ch;
+ if ((unsigned char)(ch - '0') <= 7) { /* \ooo */
+ do {
+ ch = i_peek(input);
+ if ((unsigned char)(ch - '0') > 7)
+ break;
+ *++p = ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ } while (--cnt != 0);
+ } else if (ch == 'x') { /* \xHH */
+ do {
+ ch = i_peek(input);
+ if (!isxdigit(ch))
+ break;
+ *++p = ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ } while (--cnt != 0);
+ if (cnt == 2) { /* \x but next char is "bad" */
+ ch = 'x';
+ goto unrecognized;
+ }
+ } /* else simple seq like \\ or \t */
+ *++p = '\0';
+ p = buf;
+ ch = bb_process_escape_sequence((void*)&p);
+ //bb_error_msg("buf:'%s' ch:%x", buf, ch);
+ if (ch == '\0')
+ continue; /* bash compat: $'...\0...' emits nothing */
+ } else { /* unrecognized "\z": encode both chars unless ' or " */
+ if (ch != '\'' && ch != '"') {
+ unrecognized:
+ o_addqchr(dest, '\\');
+ }
+ }
+ } /* if (\...) */
+ o_addqchr(dest, ch);
+ }
+
+ if (dest->length == start) {
+ /* $'', $'\0', $'\000\x00' and the like */
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ }
+
+ return 1;
+# undef as_string
+}
+#else
+# #define parse_dollar_squote(as_string, dest, input) 0
+#endif /* BASH_DOLLAR_SQUOTE */
+
/* Return code: 0 for OK, 1 for syntax error */
#if BB_MMU
#define parse_dollar(as_string, dest, input, quote_mask) \
@@ -4927,7 +5027,7 @@ static int parse_dollar(o_string *as_string,
{
int ch = i_peek_and_eat_bkslash_nl(input); /* first character after the $ */
- debug_printf_parse("parse_dollar entered: ch='%c'\n", ch);
+ debug_printf_parse("parse_dollar entered: ch='%c' quote_mask:0x%x\n", ch, quote_mask);
if (isalpha(ch)) {
make_var:
ch = i_getch(input);
@@ -4994,6 +5094,32 @@ static int parse_dollar(o_string *as_string,
* which check invalid constructs like ${%}.
* Oh well... let's check that the var name part is fine... */
+ if (isdigit(len_single_ch)
+ || (len_single_ch == '#' && isdigit(i_peek_and_eat_bkslash_nl(input)))
+ ) {
+ /* Execution engine uses plain xatoi_positive()
+ * to interpret ${NNN} and {#NNN},
+ * check syntax here in the parser.
+ * (bash does not support expressions in ${#NN},
+ * e.g. ${#$var} and {#1:+WORD} are not supported).
+ */
+ unsigned cnt = 9; /* max 9 digits for ${NN} and 8 for {#NN} */
+ while (1) {
+ o_addchr(dest, ch);
+ debug_printf_parse(": '%c'\n", ch);
+ ch = i_getch_and_eat_bkslash_nl(input);
+ nommu_addchr(as_string, ch);
+ if (ch == '}')
+ break;
+ if (--cnt == 0)
+ goto bad_dollar_syntax;
+ if (len_single_ch != '#' && strchr(VAR_SUBST_OPS, ch))
+ /* ${NN<op>...} is valid */
+ goto eat_until_closing;
+ if (!isdigit(ch))
+ goto bad_dollar_syntax;
+ }
+ } else
while (1) {
unsigned pos;
@@ -5004,7 +5130,6 @@ static int parse_dollar(o_string *as_string,
nommu_addchr(as_string, ch);
if (ch == '}')
break;
-
if (!isalnum(ch) && ch != '_') {
unsigned end_ch;
unsigned char last_ch;
@@ -5023,6 +5148,7 @@ static int parse_dollar(o_string *as_string,
* special var name, e.g. ${#!}.
*/
}
+ eat_until_closing:
/* Eat everything until closing '}' (or ':') */
end_ch = '}';
if (BASH_SUBSTR
@@ -5217,6 +5343,8 @@ static int encode_string(o_string *as_string,
goto again;
}
if (ch == '$') {
+ //if (parse_dollar_squote(as_string, dest, input))
+ // goto again;
if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) {
debug_printf_parse("encode_string return 0: "
"parse_dollar returned 0 (error)\n");
@@ -5237,6 +5365,11 @@ static int encode_string(o_string *as_string,
}
#endif
o_addQchr(dest, ch);
+ if (ch == SPECIAL_VAR_SYMBOL) {
+ /* Convert "^C" to corresponding special variable reference */
+ o_addchr(dest, SPECIAL_VAR_QUOTED_SVS);
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ }
goto again;
#undef as_string
}
@@ -5348,6 +5481,11 @@ static struct pipe *parse_stream(char **pstring,
if (ch == '\n')
continue; /* drop \<newline>, get next char */
nommu_addchr(&ctx.as_string, '\\');
+ if (ch == SPECIAL_VAR_SYMBOL) {
+ nommu_addchr(&ctx.as_string, ch);
+ /* Convert \^C to corresponding special variable reference */
+ goto case_SPECIAL_VAR_SYMBOL;
+ }
o_addchr(&ctx.word, '\\');
if (ch == EOF) {
/* Testcase: eval 'echo Ok\' */
@@ -5672,6 +5810,7 @@ static struct pipe *parse_stream(char **pstring,
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
switch (ch) {
+ case_SPECIAL_VAR_SYMBOL:
case SPECIAL_VAR_SYMBOL:
/* Convert raw ^C to corresponding special variable reference */
o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
@@ -5682,6 +5821,8 @@ static struct pipe *parse_stream(char **pstring,
o_addchr(&ctx.word, ch);
continue; /* get next char */
case '$':
+ if (parse_dollar_squote(&ctx.as_string, &ctx.word, input))
+ continue; /* get next char */
if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) {
debug_printf_parse("parse_stream parse error: "
"parse_dollar returned 0 (error)\n");
@@ -5809,7 +5950,6 @@ static struct pipe *parse_stream(char **pstring,
if (ctx.ctx_res_w == RES_MATCH)
goto case_semi;
#endif
-
case '}':
/* proper use of this character is caught by end_trigger:
* if we see {, we call parse_group(..., end_trigger='}')
@@ -6125,6 +6265,8 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
continue;
}
if (ch == '$') {
+ if (parse_dollar_squote(NULL, &dest, &input))
+ continue;
if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) {
debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
goto ret;
@@ -6324,6 +6466,18 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
/* ${var/[/]pattern[/repl]} helpers */
static char *strstr_pattern(char *val, const char *pattern, int *size)
{
+ int sz = strcspn(pattern, "*?[\\");
+ if (pattern[sz] == '\0') {
+ /* Optimization for trivial patterns.
+ * Testcase for very slow replace (performs about 22k replaces):
+ * x=::::::::::::::::::::::
+ * x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;echo ${#x}
+ * echo "${x//:/|}"
+ */
+ *size = sz;
+ return strstr(val, pattern);
+ }
+
while (1) {
char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF);
debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end);
@@ -10799,10 +10953,17 @@ static int FAST_FUNC builtin_read(char **argv)
*/
params.read_flags = getopt32(argv,
# if BASH_READ_D
- "!srn:p:t:u:d:", &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u, &params.opt_d
+ IF_NOT_HUSH_BASH_COMPAT("^")
+ "!srn:p:t:u:d:" IF_NOT_HUSH_BASH_COMPAT("\0" "-1"/*min 1 arg*/),
+ &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u, &params.opt_d
# else
- "!srn:p:t:u:", &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u
+ IF_NOT_HUSH_BASH_COMPAT("^")
+ "!srn:p:t:u:" IF_NOT_HUSH_BASH_COMPAT("\0" "-1"/*min 1 arg*/),
+ &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u
# endif
+//TODO: print "read: need variable name"
+//for the case of !BASH "read" with no args (now it fails silently)
+//(or maybe extend getopt32() to emit a message if "-1" fails)
);
if ((uint32_t)params.read_flags == (uint32_t)-1)
return EXIT_FAILURE;
diff --git a/shell/hush_test/hush-misc/control_char3.right b/shell/hush_test/hush-misc/control_char3.right
new file mode 100644
index 000000000..94b4f8699
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char3.right
@@ -0,0 +1 @@
+hush: can't execute '': No such file or directory
diff --git a/shell/hush_test/hush-misc/control_char3.tests b/shell/hush_test/hush-misc/control_char3.tests
new file mode 100755
index 000000000..4359db3f3
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char3.tests
@@ -0,0 +1,2 @@
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+$THIS_SH -c '\' SHELL
diff --git a/shell/hush_test/hush-misc/control_char4.right b/shell/hush_test/hush-misc/control_char4.right
new file mode 100644
index 000000000..698e21427
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char4.right
@@ -0,0 +1 @@
+hush: can't execute '-': No such file or directory
diff --git a/shell/hush_test/hush-misc/control_char4.tests b/shell/hush_test/hush-misc/control_char4.tests
new file mode 100755
index 000000000..48010f154
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char4.tests
@@ -0,0 +1,2 @@
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+$THIS_SH -c '"-"' SHELL
diff --git a/shell/hush_test/hush-parsing/bkslash_newline4.right b/shell/hush_test/hush-parsing/bkslash_newline4.right
new file mode 100644
index 000000000..2110716d1
--- /dev/null
+++ b/shell/hush_test/hush-parsing/bkslash_newline4.right
@@ -0,0 +1,4 @@
+1:1
+22:22
+3:3
+Ok:0
diff --git a/shell/hush_test/hush-parsing/bkslash_newline4.tests b/shell/hush_test/hush-parsing/bkslash_newline4.tests
new file mode 100755
index 000000000..c8f5037c4
--- /dev/null
+++ b/shell/hush_test/hush-parsing/bkslash_newline4.tests
@@ -0,0 +1,14 @@
+set -- 1 22 333
+echo 1:$\
+1
+echo 22:$\
+{\
+2\
+}
+echo 3:$\
+{\
+#\
+3\
+}
+echo Ok:$\
+?
diff --git a/shell/hush_test/hush-psubst/falsetick.right b/shell/hush_test/hush-psubst/falsetick.right
index 0b98fb778..d2d1a2880 100644
--- a/shell/hush_test/hush-psubst/falsetick.right
+++ b/shell/hush_test/hush-psubst/falsetick.right
@@ -22,6 +22,3 @@ hush: can't open '/does/not/exist': No such file or directory
1
hush: can't open '/does/not/exist': No such file or directory
1
-hush: can't open '/does/not/exist': No such file or directory
-1
-Done: a=b
diff --git a/shell/hush_test/hush-psubst/falsetick.tests b/shell/hush_test/hush-psubst/falsetick.tests
index 44d2eae8b..d2b93695e 100755
--- a/shell/hush_test/hush-psubst/falsetick.tests
+++ b/shell/hush_test/hush-psubst/falsetick.tests
@@ -17,6 +17,3 @@ true; a=`exit 2` >/does/not/exist; echo $?
false; a=`exit 2` >/does/not/exist; echo $?
true; a=$(exit 2) >/does/not/exist; echo $?
false; a=$(exit 2) >/does/not/exist; echo $?
-# ...and assignment still happens despite redirect error:
-true; a=$(echo b) >/does/not/exist; echo $?
-echo "Done: a=$a"
diff --git a/shell/hush_test/hush-psubst/falsetick3.right b/shell/hush_test/hush-psubst/falsetick3.right
new file mode 100644
index 000000000..327849a31
--- /dev/null
+++ b/shell/hush_test/hush-psubst/falsetick3.right
@@ -0,0 +1,3 @@
+hush: can't open '/does/not/exist': No such file or directory
+1
+Done: a=b
diff --git a/shell/hush_test/hush-psubst/falsetick3.tests b/shell/hush_test/hush-psubst/falsetick3.tests
new file mode 100755
index 000000000..cd185335e
--- /dev/null
+++ b/shell/hush_test/hush-psubst/falsetick3.tests
@@ -0,0 +1,3 @@
+# assignment still happens despite redirect error
+true; a=$(echo b) >/does/not/exist; echo $?
+echo "Done: a=$a"
diff --git a/shell/hush_test/hush-quoting/dollar_squote_bash1.right b/shell/hush_test/hush-quoting/dollar_squote_bash1.right
new file mode 100644
index 000000000..9f4e25efa
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_squote_bash1.right
@@ -0,0 +1,10 @@
+a b
+$'a\tb'
+a
+b c
+def
+a'b c"d e\f
+a3b c3b e33f
+a\80b c08b
+a3b c30b
+x y
diff --git a/shell/hush_test/hush-quoting/dollar_squote_bash1.tests b/shell/hush_test/hush-quoting/dollar_squote_bash1.tests
new file mode 100755
index 000000000..6fc411b93
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_squote_bash1.tests
@@ -0,0 +1,8 @@
+echo $'a\tb'
+echo "$'a\tb'"
+echo $'a\nb' $'c\nd''ef'
+echo $'a\'b' $'c\"d' $'e\\f'
+echo $'a\63b' $'c\063b' $'e\0633f'
+echo $'a\80b' $'c\608b'
+echo $'a\x33b' $'c\x330b'
+echo $'x\x9y'
diff --git a/shell/hush_test/hush-quoting/dollar_squote_bash2.right b/shell/hush_test/hush-quoting/dollar_squote_bash2.right
new file mode 100644
index 000000000..f7a1731dd
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_squote_bash2.right
@@ -0,0 +1,6 @@
+strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstr
+strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstr
+80:\
+81:\
+82:\
+Done:0
diff --git a/shell/hush_test/hush-quoting/dollar_squote_bash2.tests b/shell/hush_test/hush-quoting/dollar_squote_bash2.tests
new file mode 100755
index 000000000..449772813
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_squote_bash2.tests
@@ -0,0 +1,10 @@
+# Embedded NULs
+echo $'str\x00'strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstr
+echo $'str\000'strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstr
+
+# The chars after '\' are hex 0x80,81,82...
+echo 80:$'\'
+echo 81:$'\'
+echo 82:$'\'
+
+echo Done:$?
diff --git a/shell/hush_test/hush-vars/var6.right b/shell/hush_test/hush-vars/var6.right
index 40e67fdf5..5e28d2fab 100644
--- a/shell/hush_test/hush-vars/var6.right
+++ b/shell/hush_test/hush-vars/var6.right
@@ -1,2 +1,2 @@
-hush: invalid number '1q'
+hush: syntax error: unterminated ${name}
hush: syntax error: unterminated ${name}
diff --git a/shell/hush_test/hush-vars/var6.tests b/shell/hush_test/hush-vars/var6.tests
index aea36d62d..626e60ee1 100755
--- a/shell/hush_test/hush-vars/var6.tests
+++ b/shell/hush_test/hush-vars/var6.tests
@@ -1,4 +1,5 @@
# reject invalid vars
-"$THIS_SH" -c 'echo ${1q}'
-"$THIS_SH" -c 'echo ${&}'
-#"$THIS_SH" -c 'echo ${$}' -- this is valid as it's the same as $$
+# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
+"$THIS_SH" -c 'echo ${1q}' SHELL
+"$THIS_SH" -c 'echo ${&}' SHELL
+#"$THIS_SH" -c 'echo ${$}' SHELL -- this is valid as it's the same as $$
diff --git a/shell/hush_test/hush-vars/var_bash7.right b/shell/hush_test/hush-vars/var_bash7.right
new file mode 100644
index 000000000..223b7836f
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash7.right
@@ -0,0 +1 @@
+B
diff --git a/shell/hush_test/hush-vars/var_bash7.tests b/shell/hush_test/hush-vars/var_bash7.tests
new file mode 100755
index 000000000..c4ce03f7f
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash7.tests
@@ -0,0 +1 @@
+x=AB; echo "${x#$'\x41'}"
diff --git a/shell/match.c b/shell/match.c
index ee8abb2db..90f77546d 100644
--- a/shell/match.c
+++ b/shell/match.c
@@ -64,11 +64,10 @@ char* FAST_FUNC scan_and_match(char *string, const char *pattern, unsigned flags
}
while (loc != end) {
- char c;
int r;
- c = *loc;
if (flags & SCAN_MATCH_LEFT_HALF) {
+ char c = *loc;
*loc = '\0';
r = fnmatch(pattern, string, 0);
//bb_error_msg("fnmatch('%s','%s',0):%d", pattern, string, r);
diff --git a/shell/shell_common.c b/shell/shell_common.c
index f95a35e8b..2e36d9208 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -59,7 +59,7 @@ shell_builtin_read(struct builtin_read_params *params)
while (*pp) {
if (endofname(*pp)[0] != '\0') {
/* Mimic bash message */
- bb_error_msg("read: '%s': not a valid identifier", *pp);
+ bb_error_msg("read: '%s': bad variable name", *pp);
return (const char *)(uintptr_t)1;
}
pp++;
@@ -230,7 +230,7 @@ shell_builtin_read(struct builtin_read_params *params)
* without variable names (bash compat).
* Thus, "read" and "read REPLY" are not the same.
*/
- if (!params->opt_d && argv[0]) {
+ if (argv[0]) {
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
const char *is_ifs = strchr(ifs, c);
if (startword && is_ifs) {
diff --git a/sysklogd/klogd.c b/sysklogd/klogd.c
index 82596bc0b..df0edee0a 100644
--- a/sysklogd/klogd.c
+++ b/sysklogd/klogd.c
@@ -226,7 +226,7 @@ int klogd_main(int argc UNUSED_PARAM, char **argv)
signal(SIGHUP, SIG_IGN);
/* We want klogd_read to not be restarted, thus _norestart: */
- bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo);
+ bb_signals_norestart(BB_FATAL_SIGS, record_signo);
syslog(LOG_NOTICE, "klogd started: %s", bb_banner);
diff --git a/sysklogd/logger.c b/sysklogd/logger.c
index 9422b6ea7..04b2c8e3b 100644
--- a/sysklogd/logger.c
+++ b/sysklogd/logger.c
@@ -26,7 +26,7 @@
//usage: "Write MESSAGE (or stdin) to syslog\n"
//usage: "\n -s Log to stderr as well as the system log"
//usage: "\n -t TAG Log using the specified tag (defaults to user name)"
-//usage: "\n -p PRIO Priority (numeric or facility.level pair)"
+//usage: "\n -p PRIO Priority (number or FACILITY.LEVEL pair)"
//usage:
//usage:#define logger_example_usage
//usage: "$ logger \"hello\"\n"
diff --git a/testsuite/awk.tests b/testsuite/awk.tests
index cf9b722dc..f53b1efe2 100755
--- a/testsuite/awk.tests
+++ b/testsuite/awk.tests
@@ -45,6 +45,16 @@ testing "awk handles empty function f(arg){}" \
"" ""
prg='
+function empty_fun(){}
+END {empty_fun()
+ print "Ok"
+}'
+testing "awk handles empty function f(){}" \
+ "awk '$prg'" \
+ "Ok\n" \
+ "" ""
+
+prg='
function outer_fun() {
return 1
}
@@ -71,6 +81,23 @@ testing "awk properly handles undefined function" \
"L1\n\nawk: cmd. line:5: Call to undefined function\n" \
"" ""
+prg='
+BEGIN {
+ v=1
+ a=2
+ print v (a)
+}'
+testing "awk 'v (a)' is not a function call, it is a concatenation" \
+ "awk '$prg' 2>&1" \
+ "12\n" \
+ "" ""
+
+prg='func f(){print"F"};func g(){print"G"};BEGIN{f(g(),g())}'
+testing "awk unused function args are evaluated" \
+ "awk '$prg' 2>&1" \
+ "G\nG\nF\n" \
+ "" ""
+
optional DESKTOP
testing "awk hex const 1" "awk '{ print or(0xffffffff,1) }'" "4294967295\n" "" "\n"
@@ -352,19 +379,14 @@ testing "awk -e and ARGC" \
""
SKIP=
-# The examples are in fact not valid awk programs (break/continue
-# can only be used inside loops).
-# But we do accept them outside of loops.
-# We had a bug with misparsing "break ; else" sequence.
-# Test that *that* bug is fixed, using simplest possible scripts:
testing "awk break" \
"awk -f - 2>&1; echo \$?" \
- "0\n" \
+ "awk: -:1: 'break' not in a loop\n1\n" \
"" \
'BEGIN { if (1) break; else a = 1 }'
testing "awk continue" \
"awk -f - 2>&1; echo \$?" \
- "0\n" \
+ "awk: -:1: 'continue' not in a loop\n1\n" \
"" \
'BEGIN { if (1) continue; else a = 1 }'
@@ -388,6 +410,14 @@ testing "awk printf('%c') can output NUL" \
"awk '{printf(\"hello%c null\n\", 0)}'" "hello\0 null\n" "" "\n"
SKIP=
+optional FEATURE_AWK_GNU_EXTENSIONS
+testing "awk printf('%-10c') can output NUL" \
+ "awk 'BEGIN { printf \"[%-10c]\n\", 0 }' | od -tx1" "\
+0000000 5b 00 20 20 20 20 20 20 20 20 20 5d 0a
+0000015
+" "" ""
+SKIP=
+
# testing "description" "command" "result" "infile" "stdin"
testing 'awk negative field access' \
'awk 2>&1 -- '\''{ $(-1) }'\' \
@@ -418,4 +448,19 @@ testing 'awk $NF is empty' \
'' \
'a=====123='
+testing "awk exit N propagates through END's exit" \
+ "awk 'BEGIN { exit 42 } END { exit }'; echo \$?" \
+ "42\n" \
+ '' ''
+
+testing "awk print + redirect" \
+ "awk 'BEGIN { print \"STDERR %s\" >\"/dev/stderr\" }' 2>&1" \
+ "STDERR %s\n" \
+ '' ''
+
+testing "awk \"cmd\" | getline" \
+ "awk 'BEGIN { \"echo HELLO\" | getline; print }'" \
+ "HELLO\n" \
+ '' ''
+
exit $FAILCOUNT
diff --git a/testsuite/cut.tests b/testsuite/cut.tests
index 110340277..d705b91c3 100755
--- a/testsuite/cut.tests
+++ b/testsuite/cut.tests
@@ -15,4 +15,68 @@ testing "cut '-' (stdin) and multi file handling" \
"the quick brown fox\n" \
"jumps over the lazy dog\n" \
+abc="\
+one:two:three:four:five:six:seven
+alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu
+the quick brown fox jumps over the lazy dog
+"
+
+testing "cut -b a,a,a" "cut -b 3,3,3 input" "e\np\ne\n" "$abc" ""
+
+testing "cut -b overlaps" "cut -b 1-3,2-5,7-9,9-10 input" \
+ "one:to:th\nalphabeta\nthe qick \n" "$abc" ""
+testing "-b encapsulated" "cut -b 3-8,4-6 input" "e:two:\npha:be\ne quic\n" \
+ "$abc" ""
+# --output-delimiter not implemnted (yet?)
+#testing "cut -bO overlaps" \
+# "cut --output-delimiter ' ' -b 1-3,2-5,7-9,9-10 input" \
+# "one:t o:th\nalpha beta\nthe q ick \n" "$abc" ""
+testing "cut high-low error" "cut -b 8-3 abc.txt 2>/dev/null || echo err" "err\n" \
+ "$abc" ""
+
+testing "cut -c a-b" "cut -c 4-10 input" ":two:th\nha:beta\n quick \n" "$abc" ""
+testing "cut -c a-" "cut -c 41- input" "\ntheta:iota:kappa:lambda:mu\ndog\n" "$abc" ""
+testing "cut -c -b" "cut -c -39 input" \
+ "one:two:three:four:five:six:seven\nalpha:beta:gamma:delta:epsilon:zeta:eta\nthe quick brown fox jumps over the lazy\n" \
+ "$abc" ""
+testing "cut -c a" "cut -c 40 input" "\n:\n \n" "$abc" ""
+testing "cut -c a,b-c,d" "cut -c 3,5-7,10 input" "etwoh\npa:ba\nequi \n" "$abc" ""
+
+testing "cut -f a-" "cut -d ':' -f 5- input" "five:six:seven\nepsilon:zeta:eta:theta:iota:kappa:lambda:mu\nthe quick brown fox jumps over the lazy dog\n" "$abc" ""
+
+testing "cut show whole line with no delim" "cut -d ' ' -f 3 input" \
+ "one:two:three:four:five:six:seven\nalpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu\nbrown\n" "$abc" ""
+
+testing "cut with echo, -c (a-b)" "echo 'ref_categorie=test' | cut -c 1-15 " "ref_categorie=t\n" "" ""
+testing "cut with echo, -c (a)" "echo 'ref_categorie=test' | cut -c 14" "=\n" "" ""
+
+testing "cut with -c (a,b,c)" "cut -c 4,5,20 input" "det\n" "abcdefghijklmnopqrstuvwxyz" ""
+
+testing "cut with -b (a,b,c)" "cut -b 4,5,20 input" "det\n" "abcdefghijklmnopqrstuvwxyz" ""
+
+input="\
+406378:Sales:Itorre:Jan
+031762:Marketing:Nasium:Jim
+636496:Research:Ancholie:Mel
+396082:Sales:Jucacion:Ed
+"
+testing "cut with -d -f(:) -s" "cut -d: -f3 -s input" "Itorre\nNasium\nAncholie\nJucacion\n" "$input" ""
+testing "cut with -d -f( ) -s" "cut -d' ' -f3 -s input && echo yes" "yes\n" "$input" ""
+testing "cut with -d -f(a) -s" "cut -da -f3 -s input" "n\nsium:Jim\n\ncion:Ed\n" "$input" ""
+testing "cut with -d -f(a) -s -n" "cut -da -f3 -s -n input" "n\nsium:Jim\n\ncion:Ed\n" "$input" ""
+
+# substitute for awk
+testing "cut -DF" "cut -DF 2,7,5" \
+ "said and your\nare\nis demand. supply\nforecast :\nyou you better,\n\nEm: Took hate\n" "" \
+"Bother, said Pooh. It's your husband, and he has a gun.
+Cheerios are donut seeds.
+Talk is cheap because supply exceeds demand.
+Weather forecast for tonight : dark.
+Apple: you can buy better, but you can't pay more.
+Subcalifragilisticexpialidocious.
+Auntie Em: Hate you, hate Kansas. Took the dog. Dorothy."
+
+testing "cut empty field" "cut -d ':' -f 1-3" "a::b\n" "" "a::b\n"
+testing "cut empty field 2" "cut -d ':' -f 3-5" "b::c\n" "" "a::b::c:d\n"
+
exit $FAILCOUNT
diff --git a/testsuite/mv/mv-files-to-dir-2 b/testsuite/mv/mv-files-to-dir-2
new file mode 100644
index 000000000..e189ebb6f
--- /dev/null
+++ b/testsuite/mv/mv-files-to-dir-2
@@ -0,0 +1,16 @@
+echo file number one > file1
+echo file number two > file2
+ln -s file2 link1
+mkdir dir1
+TZ=UTC0 touch -d '2000-01-30 05:24:08' dir1/file3
+mkdir there
+busybox mv -t there file1 file2 link1 dir1
+test -f there/file1
+test -f there/file2
+test -f there/dir1/file3
+test -L there/link1
+test xfile2 = x`readlink there/link1`
+test ! -e file1
+test ! -e file2
+test ! -e link1
+test ! -e dir1/file3
diff --git a/testsuite/unlzma.tests b/testsuite/unlzma.tests
index 0e98afe09..fcc6e9441 100755
--- a/testsuite/unlzma.tests
+++ b/testsuite/unlzma.tests
@@ -8,14 +8,23 @@
# Damaged encrypted streams
testing "unlzma (bad archive 1)" \
- "unlzma <unlzma_issue_1.lzma >/dev/null; echo \$?" \
-"1
+ "unlzma <unlzma_issue_1.lzma 2>&1 >/dev/null; echo \$?" \
+"unlzma: corrupted data
+1
" "" ""
# Damaged encrypted streams
testing "unlzma (bad archive 2)" \
- "unlzma <unlzma_issue_2.lzma >/dev/null; echo \$?" \
-"1
+ "unlzma <unlzma_issue_2.lzma 2>&1 >/dev/null; echo \$?" \
+"unlzma: corrupted data
+1
+" "" ""
+
+# Damaged encrypted streams
+testing "unlzma (bad archive 3)" \
+ "unlzma <unlzma_issue_3.lzma 2>&1 >/dev/null; echo \$?" \
+"unlzma: corrupted data
+1
" "" ""
exit $FAILCOUNT
diff --git a/testsuite/unlzma_issue_3.lzma b/testsuite/unlzma_issue_3.lzma
new file mode 100644
index 000000000..cc60f29e4
--- /dev/null
+++ b/testsuite/unlzma_issue_3.lzma
Binary files differ
diff --git a/util-linux/blockdev.c b/util-linux/blockdev.c
index 20a031377..3b550220a 100644
--- a/util-linux/blockdev.c
+++ b/util-linux/blockdev.c
@@ -25,8 +25,11 @@
//usage: "\n --getbsz Get block size"
//usage: "\n --setbsz BYTES Set block size"
//usage: "\n --getsz Get device size in 512-byte sectors"
-/*//usage: "\n --getsize Get device size in sectors (deprecated)"*/
+///////: "\n --getsize Get device size in sectors (deprecated)"
+///////^^^^^ supported, but not shown in help ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//usage: "\n --getsize64 Get device size in bytes"
+//usage: "\n --getra Get readahead in 512-byte sectors"
+//usage: "\n --setra SECTORS Set readahead"
//usage: "\n --flushbufs Flush buffers"
//usage: "\n --rereadpt Reread partition table"
// util-linux 2.31 also has:
@@ -57,6 +60,9 @@ static const char bdcmd_names[] ALIGN1 =
"getsz" "\0"
"getsize" "\0"
"getsize64" "\0"
+ "getra" "\0"
+ "setra" "\0"
+#define CMD_SETRA 10
"flushbufs" "\0"
"rereadpt" "\0"
;
@@ -70,6 +76,8 @@ static const uint32_t bdcmd_ioctl[] ALIGN4 = {
BLKGETSIZE64, //getsz
BLKGETSIZE, //getsize
BLKGETSIZE64, //getsize64
+ BLKRAGET, //getra
+ BLKRASET, //setra
BLKFLSBUF, //flushbufs
BLKRRPART, //rereadpt
};
@@ -95,6 +103,8 @@ static const uint8_t bdcmd_flags[] ALIGN1 = {
ARG_U64 + FL_SCALE512, //getsz
ARG_ULONG, //getsize
ARG_U64, //getsize64
+ ARG_ULONG, //getra
+ ARG_ULONG + FL_NORESULT, //setra
ARG_NONE + FL_NORESULT, //flushbufs
ARG_NONE + FL_NORESULT, //rereadpt
};
@@ -130,8 +140,9 @@ int blockdev_main(int argc UNUSED_PARAM, char **argv)
/* setrw translates to BLKROSET(0), most other ioctls don't care... */
/* ...setro will do BLKROSET(1) */
u64 = (bdcmd == CMD_SETRO);
- if (bdcmd == CMD_SETBSZ) {
+ if (bdcmd == CMD_SETBSZ || bdcmd == CMD_SETRA) {
/* ...setbsz is BLKBSZSET(bytes) */
+ /* ...setra is BLKRASET(512 bytes) */
u64 = xatoi_positive(*++argv);
}
@@ -145,8 +156,11 @@ int blockdev_main(int argc UNUSED_PARAM, char **argv)
#if BB_BIG_ENDIAN
/* Store data properly wrt data size.
* (1) It's no-op for little-endian.
- * (2) it's no-op for 0 and -1. Only --setro uses arg != 0 and != -1,
- * and it is ARG_INT. --setbsz USER_VAL is also ARG_INT.
+ * (2) it's no-op for 0 and -1.
+ * --setro uses arg != 0 and != -1, and it is ARG_INT
+ * --setbsz USER_VAL is also ARG_INT
+ * --setra USER_VAL is ARG_ULONG, but it is passed by value,
+ * not reference (see below in ioctl call).
* Thus, we don't need to handle ARG_ULONG.
*/
switch (flags & ARG_MASK) {
@@ -161,7 +175,11 @@ int blockdev_main(int argc UNUSED_PARAM, char **argv)
}
#endif
- if (ioctl(fd, bdcmd_ioctl[bdcmd], &ioctl_val_on_stack.u64) == -1)
+ if (ioctl(fd, bdcmd_ioctl[bdcmd],
+ bdcmd == CMD_SETRA
+ ? (void*)(uintptr_t)u64 /* BLKRASET passes _value_, not pointer to it */
+ : &ioctl_val_on_stack.u64
+ ) == -1)
bb_simple_perror_msg_and_die(*argv);
/* Fetch it into register(s) */
diff --git a/util-linux/dmesg.c b/util-linux/dmesg.c
index dc4e57169..6670b84de 100644
--- a/util-linux/dmesg.c
+++ b/util-linux/dmesg.c
@@ -46,7 +46,7 @@
//kbuild:lib-$(CONFIG_DMESG) += dmesg.o
//usage:#define dmesg_trivial_usage
-//usage: "[-c] [-n LEVEL] [-s SIZE]"
+//usage: "[-cr] [-n LEVEL] [-s SIZE]"
//usage:#define dmesg_full_usage "\n\n"
//usage: "Print or control the kernel ring buffer\n"
//usage: "\n -c Clear ring buffer after printing"
diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c
index c50ceead1..1c2a7d683 100644
--- a/util-linux/fdisk.c
+++ b/util-linux/fdisk.c
@@ -232,8 +232,8 @@ struct pte {
};
#define unable_to_open "can't open '%s'"
-#define unable_to_read "can't read from %s"
-#define unable_to_seek "can't seek on %s"
+#define unable_to_read "can't read '%s'"
+#define unable_to_seek "can't seek '%s'"
enum label_type {
LABEL_DOS, LABEL_SUN, LABEL_SGI, LABEL_AIX, LABEL_OSF, LABEL_GPT
diff --git a/util-linux/flock.c b/util-linux/flock.c
index 12c16013c..1ed752a80 100644
--- a/util-linux/flock.c
+++ b/util-linux/flock.c
@@ -14,7 +14,7 @@
//kbuild:lib-$(CONFIG_FLOCK) += flock.o
//usage:#define flock_trivial_usage
-//usage: "[-sxun] FD|{FILE [-c] PROG ARGS}"
+//usage: "[-sxun] FD | { FILE [-c] PROG ARGS }"
//usage:#define flock_full_usage "\n\n"
//usage: "[Un]lock file descriptor, or lock FILE, run PROG\n"
//usage: "\n -s Shared lock"
diff --git a/util-linux/hexdump_xxd.c b/util-linux/hexdump_xxd.c
index 29bbc6633..fe78f6242 100644
--- a/util-linux/hexdump_xxd.c
+++ b/util-linux/hexdump_xxd.c
@@ -41,15 +41,17 @@
// -u use upper case hex letters.
//usage:#define xxd_trivial_usage
-//usage: "[-pr] [-g N] [-c N] [-n LEN] [-s OFS] [FILE]"
+//usage: "[-pri] [-g N] [-c N] [-n LEN] [-s OFS] [-o OFS] [FILE]"
//usage:#define xxd_full_usage "\n\n"
//usage: "Hex dump FILE (or stdin)\n"
//usage: "\n -g N Bytes per group"
//usage: "\n -c N Bytes per line"
//usage: "\n -p Show only hex bytes, assumes -c30"
+//usage: "\n -i C include file style"
// exactly the same help text lines in hexdump and xxd:
//usage: "\n -l LENGTH Show only first LENGTH bytes"
//usage: "\n -s OFFSET Skip OFFSET bytes"
+//usage: "\n -o OFFSET Add OFFSET to displayed offset"
//usage: "\n -r Reverse (with -p, assumes no offsets in input)"
#include "libbb.h"
@@ -61,7 +63,11 @@
#define OPT_s (1 << 1)
#define OPT_a (1 << 2)
#define OPT_p (1 << 3)
-#define OPT_r (1 << 4)
+#define OPT_i (1 << 4)
+#define OPT_r (1 << 5)
+#define OPT_g (1 << 6)
+#define OPT_c (1 << 7)
+#define OPT_o (1 << 8)
static void reverse(unsigned opt, unsigned cols, const char *filename)
{
@@ -122,20 +128,30 @@ static void reverse(unsigned opt, unsigned cols, const char *filename)
fflush_stdout_and_exit(EXIT_SUCCESS);
}
+static void print_C_style(const char *p, const char *hdr)
+{
+ printf(hdr, isdigit(p[0]) ? "__" : "");
+ while (*p) {
+ bb_putchar(isalnum(*p) ? *p : '_');
+ p++;
+ }
+}
+
int xxd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int xxd_main(int argc UNUSED_PARAM, char **argv)
{
char buf[80];
dumper_t *dumper;
- char *opt_l, *opt_s;
+ char *opt_l, *opt_s, *opt_o;
unsigned bytes = 2;
unsigned cols = 0;
unsigned opt;
+ int r;
dumper = alloc_dumper();
- opt = getopt32(argv, "^" "l:s:aprg:+c:+" "\0" "?1" /* 1 argument max */,
- &opt_l, &opt_s, &bytes, &cols
+ opt = getopt32(argv, "^" "l:s:apirg:+c:+o:" "\0" "?1" /* 1 argument max */,
+ &opt_l, &opt_s, &bytes, &cols, &opt_o
);
argv += optind;
@@ -158,14 +174,24 @@ int xxd_main(int argc UNUSED_PARAM, char **argv)
//BUGGY for /proc/version (unseekable?)
}
+ if (opt & OPT_o) {
+ /* -o accepts negative numbers too */
+ dumper->xxd_displayoff = xstrtoll(opt_o, /*base:*/ 0);
+ }
+
if (opt & OPT_p) {
if (cols == 0)
cols = 30;
bytes = cols; /* -p ignores -gN */
} else {
if (cols == 0)
- cols = 16;
- bb_dump_add(dumper, "\"%08.8_ax: \""); // "address: "
+ cols = (opt & OPT_i) ? 12 : 16;
+ if (opt & OPT_i) {
+ bytes = 1; // -i ignores -gN
+ // output is " 0xXX, 0xXX, 0xXX...", add leading space
+ bb_dump_add(dumper, "\" \"");
+ } else
+ bb_dump_add(dumper, "\"%08.8_ax: \""); // "address: "
}
if (opt & OPT_r) {
@@ -173,11 +199,15 @@ int xxd_main(int argc UNUSED_PARAM, char **argv)
}
if (bytes < 1 || bytes >= cols) {
- sprintf(buf, "%u/1 \"%%02x\"", cols); // cols * "xx"
+ sprintf(buf, "%u/1 \"%%02x\"", cols); // cols * "XX"
bb_dump_add(dumper, buf);
}
else if (bytes == 1) {
- sprintf(buf, "%u/1 \"%%02x \"", cols); // cols * "xx "
+ if (opt & OPT_i)
+ sprintf(buf, "%u/1 \" 0x%%02x,\"", cols); // cols * " 0xXX,"
+//TODO: compat: omit the last comma after the very last byte
+ else
+ sprintf(buf, "%u/1 \"%%02x \"", cols); // cols * "XX "
bb_dump_add(dumper, buf);
}
else {
@@ -201,13 +231,22 @@ int xxd_main(int argc UNUSED_PARAM, char **argv)
free(bigbuf);
}
- if (!(opt & OPT_p)) {
+ if (!(opt & (OPT_p|OPT_i))) {
sprintf(buf, "\" \"%u/1 \"%%_p\"\"\n\"", cols); // " ASCII\n"
bb_dump_add(dumper, buf);
} else {
bb_dump_add(dumper, "\"\n\"");
- dumper->eofstring = "\n";
+ dumper->xxd_eofstring = "\n";
}
- return bb_dump_dump(dumper, argv);
+ if ((opt & OPT_i) && argv[0]) {
+ print_C_style(argv[0], "unsigned char %s");
+ printf("[] = {\n");
+ }
+ r = bb_dump_dump(dumper, argv);
+ if (r == 0 && (opt & OPT_i) && argv[0]) {
+ print_C_style(argv[0], "};\nunsigned int %s");
+ printf("_len = %"OFF_FMT"u;\n", dumper->address);
+ }
+ return r;
}
diff --git a/util-linux/ionice.c b/util-linux/ionice.c
index c8fb1a777..82bd309d1 100644
--- a/util-linux/ionice.c
+++ b/util-linux/ionice.c
@@ -18,11 +18,13 @@
//kbuild:lib-$(CONFIG_IONICE) += ionice.o
//usage:#define ionice_trivial_usage
-//usage: "[-c 1-3] [-n 0-7] [-p PID] [PROG ARGS]"
+//usage: "[-c 1-3] [-n 0-7] [-t] { -p PID | PROG ARGS }"
+//TODO: | -P PGID | -u UID; also -pPu can take _list of_ IDs
//usage:#define ionice_full_usage "\n\n"
//usage: "Change I/O priority and class\n"
//usage: "\n -c N Class. 1:realtime 2:best-effort 3:idle"
//usage: "\n -n N Priority"
+//usage: "\n -t Ignore errors"
#include <sys/syscall.h>
#include <asm/unistd.h>
@@ -64,14 +66,15 @@ int ionice_main(int argc UNUSED_PARAM, char **argv)
int pid = 0; /* affect own process */
int opt;
enum {
- OPT_n = 1,
- OPT_c = 2,
- OPT_p = 4,
+ OPT_n = 1 << 0,
+ OPT_c = 1 << 1,
+ OPT_p = 1 << 2,
+ OPT_t = 1 << 3,
};
- /* Numeric params */
/* '+': stop at first non-option */
- opt = getopt32(argv, "+n:+c:+p:+", &pri, &ioclass, &pid);
+ /* numeric params for -n -c -p */
+ opt = getopt32(argv, "+""n:+c:+p:+t", &pri, &ioclass, &pid);
argv += optind;
if (opt & OPT_c) {
@@ -104,7 +107,8 @@ int ionice_main(int argc UNUSED_PARAM, char **argv)
//pri, ioclass, pri | (ioclass << IOPRIO_CLASS_SHIFT));
pri |= (ioclass << IOPRIO_CLASS_SHIFT);
if (ioprio_set(IOPRIO_WHO_PROCESS, pid, pri) == -1)
- bb_perror_msg_and_die("ioprio_%cet", 's');
+ if (!(opt & OPT_t))
+ bb_perror_msg_and_die("ioprio_%cet", 's');
if (argv[0]) {
BB_EXECVP_or_die(argv);
}
diff --git a/util-linux/mountpoint.c b/util-linux/mountpoint.c
index a44cf6013..28b1e7a54 100644
--- a/util-linux/mountpoint.c
+++ b/util-linux/mountpoint.c
@@ -19,13 +19,14 @@
//kbuild:lib-$(CONFIG_MOUNTPOINT) += mountpoint.o
//usage:#define mountpoint_trivial_usage
-//usage: "[-q] <[-dn] DIR | -x DEVICE>"
+//usage: "[-q] { [-dn] DIR | -x DEVICE }"
//usage:#define mountpoint_full_usage "\n\n"
-//usage: "Check if the directory is a mountpoint\n"
+//usage: "Check if DIR is a mountpoint\n"
//usage: "\n -q Quiet"
-//usage: "\n -d Print major/minor device number of the filesystem"
+//usage: "\n -d Print major:minor of the filesystem"
//usage: "\n -n Print device name of the filesystem"
-//usage: "\n -x Print major/minor device number of the blockdevice"
+//////// -n is not supported by util-linux-2.36.1 ^^^^^^^^^^^^^^^^^^
+//usage: "\n -x Print major:minor of DEVICE"
//usage:
//usage:#define mountpoint_example_usage
//usage: "$ mountpoint /proc\n"
diff --git a/util-linux/readprofile.c b/util-linux/readprofile.c
index 32d9987e7..f11c62292 100644
--- a/util-linux/readprofile.c
+++ b/util-linux/readprofile.c
@@ -44,8 +44,8 @@
//usage:#define readprofile_trivial_usage
//usage: "[OPTIONS]"
//usage:#define readprofile_full_usage "\n\n"
-//usage: " -m mapfile (Default: /boot/System.map)"
-//usage: "\n -p profile (Default: /proc/profile)"
+//usage: " -m MAPFILE (Default: /boot/System.map)"
+//usage: "\n -p PROFILE (Default: /proc/profile)"
//usage: "\n -M NUM Set the profiling multiplier to NUM"
//usage: "\n -i Print only info about the sampling step"
//usage: "\n -v Verbose"
diff --git a/util-linux/renice.c b/util-linux/renice.c
index a318ffce0..fc72550f4 100644
--- a/util-linux/renice.c
+++ b/util-linux/renice.c
@@ -29,7 +29,7 @@
//kbuild:lib-$(CONFIG_RENICE) += renice.o
//usage:#define renice_trivial_usage
-//usage: "[-n] PRIORITY [[-p | -g | -u] ID...]..."
+//usage: "[-n] PRIORITY [[-p|g|u] ID...]..."
//usage:#define renice_full_usage "\n\n"
//usage: "Change scheduling priority of a running process\n"
//usage: "\n -n Add PRIORITY to current nice value"
diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c
index f2674b5ac..901c0b8db 100644
--- a/util-linux/switch_root.c
+++ b/util-linux/switch_root.c
@@ -68,11 +68,22 @@ extern int capget(cap_user_header_t header, const cap_user_data_t data);
# define MS_MOVE 8192
#endif
+static void delete_contents(const char *directory, dev_t rootdev);
+
+static int FAST_FUNC rmrf(const char *directory, struct dirent *d, void *rootdevp)
+{
+ char *newdir = concat_subpath_file(directory, d->d_name);
+ if (newdir) { // not . or ..
+ // Recurse to delete contents
+ delete_contents(newdir, *(dev_t*)rootdevp);
+ free(newdir);
+ }
+ return 0;
+}
+
// Recursively delete contents of rootfs
static void delete_contents(const char *directory, dev_t rootdev)
{
- DIR *dir;
- struct dirent *d;
struct stat st;
// Don't descend into other filesystems
@@ -81,25 +92,7 @@ static void delete_contents(const char *directory, dev_t rootdev)
// Recursively delete the contents of directories
if (S_ISDIR(st.st_mode)) {
- dir = opendir(directory);
- if (dir) {
- while ((d = readdir(dir))) {
- char *newdir = d->d_name;
-
- // Skip . and ..
- if (DOT_OR_DOTDOT(newdir))
- continue;
-
- // Recurse to delete contents
- newdir = concat_path_file(directory, newdir);
- delete_contents(newdir, rootdev);
- free(newdir);
- }
- closedir(dir);
-
- // Directory should now be empty, zap it
- rmdir(directory);
- }
+ iterate_on_dir(directory, rmrf, &rootdev);
} else {
// It wasn't a directory, zap it
unlink(directory);
diff --git a/util-linux/taskset.c b/util-linux/taskset.c
index b542f8c83..afe2f04d2 100644
--- a/util-linux/taskset.c
+++ b/util-linux/taskset.c
@@ -34,10 +34,12 @@
//kbuild:lib-$(CONFIG_TASKSET) += taskset.o
//usage:#define taskset_trivial_usage
-//usage: "[-p] [HEXMASK] PID | PROG ARGS"
+//usage: "[-ap] [HEXMASK"IF_FEATURE_TASKSET_CPULIST(" | -c LIST")"] { PID | PROG ARGS }"
//usage:#define taskset_full_usage "\n\n"
//usage: "Set or get CPU affinity\n"
-//usage: "\n -p Operate on an existing PID"
+//usage: "\n -p Operate on PID"
+//usage: "\n -a Operate on all threads"
+//usage: "\n -c Affinity is a list, not mask"
//usage:
//usage:#define taskset_example_usage
//usage: "$ taskset 0x7 ./dgemm_test&\n"
@@ -205,42 +207,18 @@ static void print_cpulist(const ul *mask, unsigned mask_size_in_bytes)
}
#endif
-int taskset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int taskset_main(int argc UNUSED_PARAM, char **argv)
+enum {
+ OPT_p = 1 << 0,
+ OPT_a = 1 << 1,
+ OPT_c = (1 << 2) * ENABLE_FEATURE_TASKSET_CPULIST,
+};
+
+static int process_pid_str(const char *pid_str, unsigned opts, char *aff)
{
ul *mask;
unsigned mask_size_in_bytes;
- pid_t pid = 0;
const char *current_new;
- char *aff;
- unsigned opts;
- enum {
- OPT_p = 1 << 0,
- OPT_c = (1 << 1) * ENABLE_FEATURE_TASKSET_CPULIST,
- };
-
- /* NB: we mimic util-linux's taskset: -p does not take
- * an argument, i.e., "-pN" is NOT valid, only "-p N"!
- * Indeed, util-linux-2.13-pre7 uses:
- * getopt_long(argc, argv, "+pchV", ...), not "...p:..." */
-
- opts = getopt32(argv, "^+" "p"IF_FEATURE_TASKSET_CPULIST("c")
- "\0" "-1" /* at least 1 arg */);
- argv += optind;
-
- aff = *argv++;
- if (opts & OPT_p) {
- char *pid_str = aff;
- if (*argv) { /* "-p <aff> <pid> ...rest.is.ignored..." */
- pid_str = *argv; /* NB: *argv != NULL in this case */
- }
- /* else it was just "-p <pid>", and *argv == NULL */
- pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1);
- } else {
- /* <aff> <cmd...> */
- if (!*argv)
- bb_show_usage();
- }
+ pid_t pid = xatoi_positive(pid_str);
mask_size_in_bytes = SZOF_UL;
current_new = "current";
@@ -255,13 +233,12 @@ int taskset_main(int argc UNUSED_PARAM, char **argv)
#endif
printf("pid %d's %s affinity mask: "TASKSET_PRINTF_MASK"\n",
pid, current_new, from_mask(mask, mask_size_in_bytes));
- if (*argv == NULL) {
+ if (!aff) {
/* Either it was just "-p <pid>",
* or it was "-p <aff> <pid>" and we came here
* for the second time (see goto below) */
- return EXIT_SUCCESS;
+ return 0;
}
- *argv = NULL;
current_new = "new";
}
memset(mask, 0, mask_size_in_bytes);
@@ -331,8 +308,61 @@ int taskset_main(int argc UNUSED_PARAM, char **argv)
bb_perror_msg_and_die("can't %cet pid %d's affinity", 's', pid);
//bb_error_msg("set mask[0]:%lx", mask[0]);
- if (!argv[0]) /* "-p <aff> <pid> [...ignored...]" */
+ if ((opts & OPT_p) && aff) { /* "-p <aff> <pid> [...ignored...]" */
+ aff = NULL;
goto print_aff; /* print new affinity and exit */
+ }
+ return 0;
+}
+
+static int FAST_FUNC iter(const char *dn UNUSED_PARAM, struct dirent *ent, void *aff)
+{
+ if (isdigit(ent->d_name[0]))
+ return process_pid_str(ent->d_name, option_mask32, aff);
+ return 0;
+}
+
+int taskset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int taskset_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *pid_str;
+ char *aff;
+ unsigned opts;
- BB_EXECVP_or_die(argv);
+ /* NB: we mimic util-linux's taskset: -p does not take
+ * an argument, i.e., "-pN" is NOT valid, only "-p N"!
+ * Indeed, util-linux-2.13-pre7 uses:
+ * getopt_long(argc, argv, "+pchV", ...), not "...p:..." */
+
+ opts = getopt32(argv, "^+" "pa"IF_FEATURE_TASKSET_CPULIST("c")
+ "\0" "-1" /* at least 1 arg */);
+ argv += optind;
+
+ aff = *argv++;
+ if (!(opts & OPT_p)) {
+ /* <aff> <cmd...> */
+ if (!*argv)
+ bb_show_usage();
+ process_pid_str("0", opts, aff);
+ BB_EXECVP_or_die(argv);
+ }
+
+ pid_str = aff;
+ if (*argv) /* "-p <aff> <pid> ...rest.is.ignored..." */
+ pid_str = *argv;
+ else
+ aff = NULL;
+
+ if (opts & OPT_a) {
+ char *dn;
+ int r;
+
+ dn = xasprintf("/proc/%s/task", pid_str);
+ r = iterate_on_dir(dn, iter, aff);
+ IF_FEATURE_CLEAN_UP(free(dn);)
+ if (r == 0)
+ return r; /* EXIT_SUCCESS */
+ /* else: no /proc/PID/task, act as if no -a was given */
+ }
+ return process_pid_str(pid_str, opts, aff);
}