aboutsummaryrefslogtreecommitdiff
path: root/miscutils
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
committerBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
commit73b16af8feec390afbabd9356d6e5e83c0390838 (patch)
tree3730020ba2f9caeb9d7815a975af51830b51ce11 /miscutils
busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2busybox-1.13.3
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'miscutils')
-rw-r--r--miscutils/Config.in552
-rw-r--r--miscutils/Kbuild38
-rw-r--r--miscutils/adjtimex.c145
-rw-r--r--miscutils/bbconfig.c12
-rw-r--r--miscutils/chat.c444
-rw-r--r--miscutils/chrt.c123
-rw-r--r--miscutils/crond.c935
-rw-r--r--miscutils/crontab.c235
-rw-r--r--miscutils/dc.c256
-rw-r--r--miscutils/devfsd.c1801
-rw-r--r--miscutils/devmem.c128
-rw-r--r--miscutils/eject.c116
-rw-r--r--miscutils/fbsplash.c393
-rw-r--r--miscutils/fbsplash.cfg9
-rw-r--r--miscutils/hdparm.c2063
-rw-r--r--miscutils/inotifyd.c152
-rw-r--r--miscutils/last.c133
-rw-r--r--miscutils/last_fancy.c297
-rw-r--r--miscutils/less.c1801
-rw-r--r--miscutils/makedevs.c209
-rw-r--r--miscutils/man.c269
-rw-r--r--miscutils/microcom.c171
-rw-r--r--miscutils/mountpoint.c66
-rw-r--r--miscutils/mt.c140
-rw-r--r--miscutils/raidautorun.c25
-rw-r--r--miscutils/readahead.c40
-rw-r--r--miscutils/runlevel.c43
-rw-r--r--miscutils/rx.c254
-rw-r--r--miscutils/setsid.c35
-rw-r--r--miscutils/strings.c84
-rw-r--r--miscutils/taskset.c137
-rw-r--r--miscutils/time.c429
-rw-r--r--miscutils/ttysize.c44
-rw-r--r--miscutils/watchdog.c80
34 files changed, 11659 insertions, 0 deletions
diff --git a/miscutils/Config.in b/miscutils/Config.in
new file mode 100644
index 0000000..60b87c1
--- /dev/null
+++ b/miscutils/Config.in
@@ -0,0 +1,552 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Miscellaneous Utilities"
+
+config ADJTIMEX
+ bool "adjtimex"
+ default n
+ help
+ Adjtimex reads and optionally sets adjustment parameters for
+ the Linux clock adjustment algorithm.
+
+config BBCONFIG
+ bool "bbconfig"
+ default n
+ help
+ The bbconfig applet will print the config file with which
+ busybox was built.
+
+config CHAT
+ bool "chat"
+ default n
+ help
+ Simple chat utility.
+
+config FEATURE_CHAT_NOFAIL
+ bool "Enable NOFAIL expect strings"
+ depends on CHAT
+ default y
+ help
+ When enabled expect strings which are started with a dash trigger
+ no-fail mode. That is when expectation is not met within timeout
+ the script is not terminated but sends next SEND string and waits
+ for next EXPECT string. This allows to compose far more flexible
+ scripts.
+
+config FEATURE_CHAT_TTY_HIFI
+ bool "Force STDIN to be a TTY"
+ depends on CHAT
+ default n
+ help
+ Original chat always treats STDIN as a TTY device and sets for it
+ so-called raw mode. This option turns on such behaviour.
+
+config FEATURE_CHAT_IMPLICIT_CR
+ bool "Enable implicit Carriage Return"
+ depends on CHAT
+ default y
+ help
+ When enabled make chat to terminate all SEND strings with a "\r"
+ unless "\c" is met anywhere in the string.
+
+config FEATURE_CHAT_SWALLOW_OPTS
+ bool "Swallow options"
+ depends on CHAT
+ default n
+ help
+ Busybox chat require no options. To make it not fail when used
+ in place of original chat (which has a bunch of options) turn
+ this on.
+
+config FEATURE_CHAT_SEND_ESCAPES
+ bool "Support weird SEND escapes"
+ depends on CHAT
+ default n
+ help
+ Original chat uses some escape sequences in SEND arguments which
+ are not sent to device but rather performs special actions.
+ E.g. "\K" means to send a break sequence to device.
+ "\d" delays execution for a second, "\p" -- for a 1/100 of second.
+ Before turning this option on think twice: do you really need them?
+
+config FEATURE_CHAT_VAR_ABORT_LEN
+ bool "Support variable-length ABORT conditions"
+ depends on CHAT
+ default n
+ help
+ Original chat uses fixed 50-bytes length ABORT conditions. Say N here.
+
+config FEATURE_CHAT_CLR_ABORT
+ bool "Support revoking of ABORT conditions"
+ depends on CHAT
+ default n
+ help
+ Support CLR_ABORT directive.
+
+config CHRT
+ bool "chrt"
+ default n
+ help
+ manipulate real-time attributes of a process.
+ This requires sched_{g,s}etparam support in your libc.
+
+config CROND
+ bool "crond"
+ default n
+ select FEATURE_SUID
+ select FEATURE_SYSLOG
+ help
+ Crond is a background daemon that parses individual crontab
+ files and executes commands on behalf of the users in question.
+ This is a port of dcron from slackware. It uses files of the
+ format /var/spool/cron/crontabs/<username> files, for example:
+ $ cat /var/spool/cron/crontabs/root
+ # Run daily cron jobs at 4:40 every day:
+ 40 4 * * * /etc/cron/daily > /dev/null 2>&1
+
+config FEATURE_CROND_D
+ bool "Support option -d to redirect output to stderr"
+ depends on CROND
+ default n
+ help
+ -d sets loglevel to 0 (most verbose) and directs all output to stderr.
+
+config FEATURE_CROND_CALL_SENDMAIL
+ bool "Using /usr/sbin/sendmail?"
+ default n
+ depends on CROND
+ help
+ Support calling /usr/sbin/sendmail for send cmd outputs.
+
+config CRONTAB
+ bool "crontab"
+ default n
+ select FEATURE_SUID
+ help
+ Crontab manipulates the crontab for a particular user. Only
+ the superuser may specify a different user and/or crontab directory.
+ Note that Busybox binary must be setuid root for this applet to
+ work properly.
+
+config DC
+ bool "dc"
+ default n
+ help
+ Dc is a reverse-polish desk calculator which supports unlimited
+ precision arithmetic.
+
+config FEATURE_DC_LIBM
+ bool "Enable power and exp functions (requires libm)"
+ default n
+ depends on DC
+ help
+ Enable power and exp functions.
+ NOTE: This will require libm to be present for linking.
+
+config DEVFSD
+ bool "devfsd (obsolete)"
+ default n
+ select FEATURE_SYSLOG
+ help
+ This is deprecated, and will be removed at the end of 2008.
+
+ Provides compatibility with old device names on a devfs systems.
+ You should set it to true if you have devfs enabled.
+ The following keywords in devsfd.conf are supported:
+ "CLEAR_CONFIG", "INCLUDE", "OPTIONAL_INCLUDE", "RESTORE",
+ "PERMISSIONS", "EXECUTE", "COPY", "IGNORE",
+ "MKOLDCOMPAT", "MKNEWCOMPAT","RMOLDCOMPAT", "RMNEWCOMPAT".
+
+ But only if they are written UPPERCASE!!!!!!!!
+
+config DEVFSD_MODLOAD
+ bool "Adds support for MODLOAD keyword in devsfd.conf"
+ default n
+ depends on DEVFSD
+ help
+ This actually doesn't work with busybox modutils but needs
+ the external modutils.
+
+config DEVFSD_FG_NP
+ bool "Enables the -fg and -np options"
+ default n
+ depends on DEVFSD
+ help
+ -fg Run the daemon in the foreground.
+ -np Exit after parsing the configuration file.
+ Do not poll for events.
+
+config DEVFSD_VERBOSE
+ bool "Increases logging (and size)"
+ default n
+ depends on DEVFSD
+ help
+ Increases logging to stderr or syslog.
+
+config FEATURE_DEVFS
+ bool "Use devfs names for all devices (obsolete)"
+ default n
+ help
+ This is obsolete and will be going away at the end of 2008..
+
+ This tells busybox to look for names like /dev/loop/0 instead of
+ /dev/loop0. If your /dev directory has normal names instead of
+ devfs names, you don't want this.
+
+config DEVMEM
+ bool "devmem"
+ default n
+ help
+ devmem is a small program that reads and writes from physical
+ memory using /dev/mem.
+
+config EJECT
+ bool "eject"
+ default n
+ help
+ Used to eject cdroms. (defaults to /dev/cdrom)
+
+config FEATURE_EJECT_SCSI
+ bool "SCSI support"
+ default n
+ depends on EJECT
+ help
+ Add the -s option to eject, this allows to eject SCSI-Devices and
+ usb-storage devices.
+
+config FBSPLASH
+ bool "fbsplash"
+ default n
+ help
+ Shows splash image and progress bar on framebuffer device.
+ Can be used during boot phase of an embedded device. ~2kb.
+ Usage:
+ - use kernel option 'vga=xxx' or otherwise enable fb device.
+ - put somewhere fbsplash.cfg file and an image in .ppm format.
+ - $ setsid fbsplash [params] &
+ -c: hide cursor
+ -d /dev/fbN: framebuffer device (if not /dev/fb0)
+ -s path_to_image_file (can be "-" for stdin)
+ -i path_to_cfg_file (can be "-" for stdin)
+ -f path_to_fifo (can be "-" for stdin)
+ - if you want to run it only in presence of kernel parameter:
+ grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params] &
+ - commands for fifo:
+ "NN" (ASCII decimal number) - percentage to show on progress bar
+ "exit" - well you guessed it
+
+config INOTIFYD
+ bool "inotifyd"
+ default n
+ help
+ Simple inotify daemon. Reports filesystem changes. Requires
+ kernel >= 2.6.13
+
+config LAST
+ bool "last"
+ default n
+ select FEATURE_WTMP
+ help
+ 'last' displays a list of the last users that logged into the system.
+
+choice
+ prompt "Choose last implementation"
+ depends on LAST
+ default FEATURE_LAST_SMALL
+
+config FEATURE_LAST_SMALL
+ bool "small"
+ help
+ This is a small version of last with just the basic set of
+ features.
+
+config FEATURE_LAST_FANCY
+ bool "huge"
+ help
+ 'last' displays detailed information about the last users that
+ logged into the system (mimics sysvinit last). +900 bytes.
+endchoice
+
+config LESS
+ bool "less"
+ default n
+ help
+ 'less' is a pager, meaning that it displays text files. It possesses
+ a wide array of features, and is an improvement over 'more'.
+
+config FEATURE_LESS_MAXLINES
+ int "Max number of input lines less will try to eat"
+ default 9999999
+ depends on LESS
+
+config FEATURE_LESS_BRACKETS
+ bool "Enable bracket searching"
+ default y
+ depends on LESS
+ help
+ This option adds the capability to search for matching left and right
+ brackets, facilitating programming.
+
+config FEATURE_LESS_FLAGS
+ bool "Enable extra flags"
+ default y
+ depends on LESS
+ help
+ The extra flags provided do the following:
+
+ The -M flag enables a more sophisticated status line.
+ The -m flag enables a simpler status line with a percentage.
+
+config FEATURE_LESS_DASHCMD
+ bool "Enable flag changes ('-' command)"
+ default n
+ depends on LESS
+ help
+ This enables the ability to change command-line flags within
+ less itself ('-' keyboard command).
+
+config FEATURE_LESS_MARKS
+ bool "Enable marks"
+ default n
+ depends on LESS
+ help
+ Marks enable positions in a file to be stored for easy reference.
+
+config FEATURE_LESS_REGEXP
+ bool "Enable regular expressions"
+ default n
+ depends on LESS
+ help
+ Enable regular expressions, allowing complex file searches.
+
+config FEATURE_LESS_LINENUMS
+ bool "Enable dynamic switching of line numbers"
+ default n
+ depends on FEATURE_LESS_DASHCMD
+ help
+ Enable "-N" command.
+
+config FEATURE_LESS_WINCH
+ bool "Enable automatic resizing on window size changes"
+ default n
+ depends on LESS
+ help
+ Makes less track window size changes.
+
+config HDPARM
+ bool "hdparm"
+ default n
+ help
+ Get/Set hard drive parameters. Primarily intended for ATA
+ drives. Adds about 13k (or around 30k if you enable the
+ FEATURE_HDPARM_GET_IDENTITY option)....
+
+config FEATURE_HDPARM_GET_IDENTITY
+ bool "Support obtaining detailed information directly from drives"
+ default y
+ depends on HDPARM
+ help
+ Enables the -I and -i options to obtain detailed information
+ directly from drives about their capabilities and supported ATA
+ feature set. If no device name is specified, hdparm will read
+ identify data from stdin. Enabling this option will add about 16k...
+
+config FEATURE_HDPARM_HDIO_SCAN_HWIF
+ bool "Register an IDE interface (DANGEROUS)"
+ default n
+ depends on HDPARM
+ help
+ Enables the 'hdparm -R' option to register an IDE interface.
+ This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_UNREGISTER_HWIF
+ bool "Un-register an IDE interface (DANGEROUS)"
+ default n
+ depends on HDPARM
+ help
+ Enables the 'hdparm -U' option to un-register an IDE interface.
+ This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_DRIVE_RESET
+ bool "Perform device reset (DANGEROUS)"
+ default n
+ depends on HDPARM
+ help
+ Enables the 'hdparm -w' option to perform a device reset.
+ This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+ bool "Tristate device for hotswap (DANGEROUS)"
+ default n
+ depends on HDPARM
+ help
+ Enables the 'hdparm -x' option to tristate device for hotswap,
+ and the '-b' option to get/set bus state. This is dangerous
+ stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_GETSET_DMA
+ bool "Get/set using_dma flag (DANGEROUS)"
+ default n
+ depends on HDPARM
+ help
+ Enables the 'hdparm -d' option to get/set using_dma flag.
+ This is dangerous stuff, so you should probably say N.
+
+config MAKEDEVS
+ bool "makedevs"
+ default n
+ help
+ 'makedevs' is a utility used to create a batch of devices with
+ one command.
+ .
+ There are two choices for command line behaviour, the interface
+ as used by LEAF/Linux Router Project, or a device table file.
+ .
+ 'leaf' is traditionally what busybox follows, it allows multiple
+ devices of a particluar type to be created per command.
+ e.g. /dev/hda[0-9]
+ Device properties are passed as command line arguments.
+ .
+ 'table' reads device properties from a file or stdin, allowing
+ a batch of unrelated devices to be made with one command.
+ User/group names are allowed as an alternative to uid/gid.
+
+choice
+ prompt "Choose makedevs behaviour"
+ depends on MAKEDEVS
+ default FEATURE_MAKEDEVS_TABLE
+
+config FEATURE_MAKEDEVS_LEAF
+ bool "leaf"
+
+config FEATURE_MAKEDEVS_TABLE
+ bool "table"
+
+endchoice
+
+config MAN
+ bool "man"
+ default n
+ help
+ Format and display manual pages.
+
+config MICROCOM
+ bool "microcom"
+ default n
+ help
+ The poor man's minicom utility for chatting with serial port devices.
+
+config MOUNTPOINT
+ bool "mountpoint"
+ default n
+ help
+ mountpoint checks if the directory is a mountpoint.
+
+config MT
+ bool "mt"
+ default n
+ help
+ mt is used to control tape devices. You can use the mt utility
+ to advance or rewind a tape past a specified number of archive
+ files on the tape.
+
+config RAIDAUTORUN
+ bool "raidautorun"
+ default n
+ help
+ raidautorun tells the kernel md driver to
+ search and start RAID arrays.
+
+config READAHEAD
+ bool "readahead"
+ default n
+ depends on LFS
+ help
+ Preload the files listed on the command line into RAM cache so that
+ subsequent reads on these files will not block on disk I/O.
+
+ This applet just calls the readahead(2) system call on each file.
+ It is mainly useful in system startup scripts to preload files
+ or executables before they are used. When used at the right time
+ (in particular when a CPU bound process is running) it can
+ significantly speed up system startup.
+
+ As readahead(2) blocks until each file has been read, it is best to
+ run this applet as a background job.
+
+config RUNLEVEL
+ bool "runlevel"
+ default n
+ help
+ find the current and previous system runlevel.
+
+ This applet uses utmp but does not rely on busybox supporing
+ utmp on purpose. It is used by e.g. emdebian via /etc/init.d/rc.
+
+config RX
+ bool "rx"
+ default n
+ help
+ Receive files using the Xmodem protocol.
+
+config SETSID
+ bool "setsid"
+ default n
+ help
+ setsid runs a program in a new session
+
+config STRINGS
+ bool "strings"
+ default n
+ help
+ strings prints the printable character sequences for each file
+ specified.
+
+config TASKSET
+ bool "taskset"
+ default n
+ help
+ Retrieve or set a processes's CPU affinity.
+ This requires sched_{g,s}etaffinity support in your libc.
+
+config FEATURE_TASKSET_FANCY
+ bool "Fancy output"
+ default y
+ depends on TASKSET
+ help
+ Add code for fancy output. This merely silences a compiler-warning
+ and adds about 135 Bytes. May be needed for machines with alot
+ of CPUs.
+
+config TIME
+ bool "time"
+ default n
+ help
+ The time command runs the specified program with the given arguments.
+ When the command finishes, time writes a message to standard output
+ giving timing statistics about this program run.
+
+config TTYSIZE
+ bool "ttysize"
+ default n
+ help
+ A replacement for "stty size". Unlike stty, can report only width,
+ only height, or both, in any order. It also does not complain on
+ error, but returns default 80x24.
+ Usage in shell scripts: width=`ttysize w`.
+
+config WATCHDOG
+ bool "watchdog"
+ default n
+ help
+ The watchdog utility is used with hardware or software watchdog
+ device drivers. It opens the specified watchdog device special file
+ and periodically writes a magic character to the device. If the
+ watchdog applet ever fails to write the magic character within a
+ certain amount of time, the watchdog device assumes the system has
+ hung, and will cause the hardware to reboot.
+
+endmenu
diff --git a/miscutils/Kbuild b/miscutils/Kbuild
new file mode 100644
index 0000000..13791ef
--- /dev/null
+++ b/miscutils/Kbuild
@@ -0,0 +1,38 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_ADJTIMEX) += adjtimex.o
+lib-$(CONFIG_BBCONFIG) += bbconfig.o
+lib-$(CONFIG_CHAT) += chat.o
+lib-$(CONFIG_CHRT) += chrt.o
+lib-$(CONFIG_CROND) += crond.o
+lib-$(CONFIG_CRONTAB) += crontab.o
+lib-$(CONFIG_DC) += dc.o
+lib-$(CONFIG_DEVFSD) += devfsd.o
+lib-$(CONFIG_DEVMEM) += devmem.o
+lib-$(CONFIG_EJECT) += eject.o
+lib-$(CONFIG_FBSPLASH) += fbsplash.o
+lib-$(CONFIG_HDPARM) += hdparm.o
+lib-$(CONFIG_INOTIFYD) += inotifyd.o
+lib-$(CONFIG_FEATURE_LAST_SMALL)+= last.o
+lib-$(CONFIG_FEATURE_LAST_FANCY)+= last_fancy.o
+lib-$(CONFIG_LESS) += less.o
+lib-$(CONFIG_MAKEDEVS) += makedevs.o
+lib-$(CONFIG_MAN) += man.o
+lib-$(CONFIG_MICROCOM) += microcom.o
+lib-$(CONFIG_MOUNTPOINT) += mountpoint.o
+lib-$(CONFIG_MT) += mt.o
+lib-$(CONFIG_RAIDAUTORUN) += raidautorun.o
+lib-$(CONFIG_READAHEAD) += readahead.o
+lib-$(CONFIG_RUNLEVEL) += runlevel.o
+lib-$(CONFIG_RX) += rx.o
+lib-$(CONFIG_SETSID) += setsid.o
+lib-$(CONFIG_STRINGS) += strings.o
+lib-$(CONFIG_TASKSET) += taskset.o
+lib-$(CONFIG_TIME) += time.o
+lib-$(CONFIG_TTYSIZE) += ttysize.o
+lib-$(CONFIG_WATCHDOG) += watchdog.o
diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c
new file mode 100644
index 0000000..07f0834
--- /dev/null
+++ b/miscutils/adjtimex.c
@@ -0,0 +1,145 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * adjtimex.c - read, and possibly modify, the Linux kernel `timex' variables.
+ *
+ * Originally written: October 1997
+ * Last hack: March 2001
+ * Copyright 1997, 2000, 2001 Larry Doolittle <LRDoolittle@lbl.gov>
+ *
+ * busyboxed 20 March 2001, Larry Doolittle <ldoolitt@recycle.lbl.gov>
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <sys/timex.h>
+
+static const uint16_t statlist_bit[] = {
+ STA_PLL,
+ STA_PPSFREQ,
+ STA_PPSTIME,
+ STA_FLL,
+ STA_INS,
+ STA_DEL,
+ STA_UNSYNC,
+ STA_FREQHOLD,
+ STA_PPSSIGNAL,
+ STA_PPSJITTER,
+ STA_PPSWANDER,
+ STA_PPSERROR,
+ STA_CLOCKERR,
+ 0
+};
+static const char statlist_name[] =
+ "PLL" "\0"
+ "PPSFREQ" "\0"
+ "PPSTIME" "\0"
+ "FFL" "\0"
+ "INS" "\0"
+ "DEL" "\0"
+ "UNSYNC" "\0"
+ "FREQHOLD" "\0"
+ "PPSSIGNAL" "\0"
+ "PPSJITTER" "\0"
+ "PPSWANDER" "\0"
+ "PPSERROR" "\0"
+ "CLOCKERR"
+;
+
+static const char ret_code_descript[] =
+ "clock synchronized" "\0"
+ "insert leap second" "\0"
+ "delete leap second" "\0"
+ "leap second in progress" "\0"
+ "leap second has occurred" "\0"
+ "clock not synchronized"
+;
+
+int adjtimex_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int adjtimex_main(int argc, char **argv)
+{
+ enum {
+ OPT_quiet = 0x1
+ };
+ unsigned opt;
+ char *opt_o, *opt_f, *opt_p, *opt_t;
+ struct timex txc;
+ int i, ret;
+ const char *descript;
+ txc.modes=0;
+
+ opt = getopt32(argv, "qo:f:p:t:",
+ &opt_o, &opt_f, &opt_p, &opt_t);
+ //if (opt & 0x1) // -q
+ if (opt & 0x2) { // -o
+ txc.offset = xatol(opt_o);
+ txc.modes |= ADJ_OFFSET_SINGLESHOT;
+ }
+ if (opt & 0x4) { // -f
+ txc.freq = xatol(opt_f);
+ txc.modes |= ADJ_FREQUENCY;
+ }
+ if (opt & 0x8) { // -p
+ txc.constant = xatol(opt_p);
+ txc.modes |= ADJ_TIMECONST;
+ }
+ if (opt & 0x10) { // -t
+ txc.tick = xatol(opt_t);
+ txc.modes |= ADJ_TICK;
+ }
+ if (argc != optind) { /* no valid non-option parameters */
+ bb_show_usage();
+ }
+
+ ret = adjtimex(&txc);
+
+ if (ret < 0) {
+ bb_perror_nomsg_and_die();
+ }
+
+ if (!(opt & OPT_quiet)) {
+ int sep;
+ const char *name;
+
+ printf(
+ " mode: %d\n"
+ "-o offset: %ld\n"
+ "-f frequency: %ld\n"
+ " maxerror: %ld\n"
+ " esterror: %ld\n"
+ " status: %d (",
+ txc.modes, txc.offset, txc.freq, txc.maxerror,
+ txc.esterror, txc.status);
+
+ /* representative output of next code fragment:
+ "PLL | PPSTIME" */
+ name = statlist_name;
+ sep = 0;
+ for (i = 0; statlist_bit[i]; i++) {
+ if (txc.status & statlist_bit[i]) {
+ if (sep)
+ fputs(" | ", stdout);
+ fputs(name, stdout);
+ sep = 1;
+ }
+ name += strlen(name) + 1;
+ }
+
+ descript = "error";
+ if (ret <= 5)
+ descript = nth_string(ret_code_descript, ret);
+ printf(")\n"
+ "-p timeconstant: %ld\n"
+ " precision: %ld\n"
+ " tolerance: %ld\n"
+ "-t tick: %ld\n"
+ " time.tv_sec: %ld\n"
+ " time.tv_usec: %ld\n"
+ " return value: %d (%s)\n",
+ txc.constant,
+ txc.precision, txc.tolerance, txc.tick,
+ (long)txc.time.tv_sec, (long)txc.time.tv_usec, ret, descript);
+ }
+
+ return 0;
+}
diff --git a/miscutils/bbconfig.c b/miscutils/bbconfig.c
new file mode 100644
index 0000000..689052e
--- /dev/null
+++ b/miscutils/bbconfig.c
@@ -0,0 +1,12 @@
+/* vi: set sw=4 ts=4: */
+/* This file was released into the public domain by Paul Fox.
+ */
+#include "libbb.h"
+#include "bbconfigopts.h"
+
+int bbconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bbconfig_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+ printf(bbconfig_config);
+ return 0;
+}
diff --git a/miscutils/chat.c b/miscutils/chat.c
new file mode 100644
index 0000000..d550c7c
--- /dev/null
+++ b/miscutils/chat.c
@@ -0,0 +1,444 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones chat utility
+ * inspired by ppp's chat
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+/*
+#define ENABLE_FEATURE_CHAT_NOFAIL 1 // +126 bytes
+#define ENABLE_FEATURE_CHAT_TTY_HIFI 0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_IMPLICIT_CR 1 // + 44 bytes
+#define ENABLE_FEATURE_CHAT_SEND_ESCAPES 0 // +103 bytes
+#define ENABLE_FEATURE_CHAT_VAR_ABORT_LEN 0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_CLR_ABORT 0 // +113 bytes
+#define ENABLE_FEATURE_CHAT_SWALLOW_OPTS 0 // + 23 bytes
+*/
+
+// default timeout: 45 sec
+#define DEFAULT_CHAT_TIMEOUT 45*1000
+// max length of "abort string",
+// i.e. device reply which causes termination
+#define MAX_ABORT_LEN 50
+
+// possible exit codes
+enum {
+ ERR_OK = 0, // all's well
+ ERR_MEM, // read too much while expecting
+ ERR_IO, // signalled or I/O error
+ ERR_TIMEOUT, // timed out while expecting
+ ERR_ABORT, // first abort condition was met
+// ERR_ABORT2, // second abort condition was met
+// ...
+};
+
+// exit code
+// N.B> 10 bytes for volatile. Why all these signals?!
+static /*volatile*/ smallint exitcode;
+
+// trap for critical signals
+static void signal_handler(UNUSED_PARAM int signo)
+{
+ // report I/O error condition
+ exitcode = ERR_IO;
+}
+
+#if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
+#define unescape(s, nocr) unescape(s)
+#endif
+static size_t unescape(char *s, int *nocr)
+{
+ char *start = s;
+ char *p = s;
+
+ while (*s) {
+ char c = *s;
+ // do we need special processing?
+ // standard escapes + \s for space and \N for \0
+ // \c inhibits terminating \r for commands and is noop for expects
+ if ('\\' == c) {
+ c = *++s;
+ if (c) {
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+ if ('c' == c) {
+ *nocr = 1;
+ goto next;
+ }
+#endif
+ if ('N' == c) {
+ c = '\0';
+ } else if ('s' == c) {
+ c = ' ';
+#if ENABLE_FEATURE_CHAT_NOFAIL
+ // unescape leading dash only
+ // TODO: and only for expect, not command string
+ } else if ('-' == c && (start + 1 == s)) {
+ //c = '-';
+#endif
+ } else {
+ c = bb_process_escape_sequence((const char **)&s);
+ s--;
+ }
+ }
+ // ^A becomes \001, ^B -- \002 and so on...
+ } else if ('^' == c) {
+ c = *++s-'@';
+ }
+ // put unescaped char
+ *p++ = c;
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+ next:
+#endif
+ // next char
+ s++;
+ }
+ *p = '\0';
+
+ return p - start;
+}
+
+
+int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chat_main(int argc UNUSED_PARAM, char **argv)
+{
+// should we dump device output? to what fd? by default no.
+// this can be controlled later via ECHO {ON|OFF} chat directive
+// int echo_fd;
+ bool echo = 0;
+ // collection of device replies which cause unconditional termination
+ llist_t *aborts = NULL;
+ // inactivity period
+ int timeout = DEFAULT_CHAT_TIMEOUT;
+ // maximum length of abort string
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+ size_t max_abort_len = 0;
+#else
+#define max_abort_len MAX_ABORT_LEN
+#endif
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+ struct termios tio0, tio;
+#endif
+ // directive names
+ enum {
+ DIR_HANGUP = 0,
+ DIR_ABORT,
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+ DIR_CLR_ABORT,
+#endif
+ DIR_TIMEOUT,
+ DIR_ECHO,
+ DIR_SAY,
+ };
+
+ // make x* functions fail with correct exitcode
+ xfunc_error_retval = ERR_IO;
+
+ // trap vanilla signals to prevent process from being killed suddenly
+ bb_signals(0
+ + (1 << SIGHUP)
+ + (1 << SIGINT)
+ + (1 << SIGTERM)
+ + (1 << SIGPIPE)
+ , signal_handler);
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+ tcgetattr(STDIN_FILENO, &tio);
+ tio0 = tio;
+ cfmakeraw(&tio);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
+#endif
+
+#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
+ getopt32(argv, "vVsSE");
+ argv += optind;
+#else
+ argv++; // goto first arg
+#endif
+ // handle chat expect-send pairs
+ while (*argv) {
+ // directive given? process it
+ int key = index_in_strings(
+ "HANGUP\0" "ABORT\0"
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+ "CLR_ABORT\0"
+#endif
+ "TIMEOUT\0" "ECHO\0" "SAY\0"
+ , *argv
+ );
+ if (key >= 0) {
+ // cache directive value
+ char *arg = *++argv;
+ // ON -> 1, anything else -> 0
+ bool onoff = !strcmp("ON", arg);
+ // process directive
+ if (DIR_HANGUP == key) {
+ // turn SIGHUP on/off
+ signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
+ } else if (DIR_ABORT == key) {
+ // append the string to abort conditions
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+ size_t len = strlen(arg);
+ if (len > max_abort_len)
+ max_abort_len = len;
+#endif
+ llist_add_to_end(&aborts, arg);
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+ } else if (DIR_CLR_ABORT == key) {
+ // remove the string from abort conditions
+ // N.B. gotta refresh maximum length too...
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+ max_abort_len = 0;
+#endif
+ for (llist_t *l = aborts; l; l = l->link) {
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+ size_t len = strlen(l->data);
+#endif
+ if (!strcmp(arg, l->data)) {
+ llist_unlink(&aborts, l);
+ continue;
+ }
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+ if (len > max_abort_len)
+ max_abort_len = len;
+#endif
+ }
+#endif
+ } else if (DIR_TIMEOUT == key) {
+ // set new timeout
+ // -1 means OFF
+ timeout = atoi(arg) * 1000;
+ // 0 means default
+ // >0 means value in msecs
+ if (!timeout)
+ timeout = DEFAULT_CHAT_TIMEOUT;
+ } else if (DIR_ECHO == key) {
+ // turn echo on/off
+ // N.B. echo means dumping output
+ // from stdin (device) to stderr
+ echo = onoff;
+//TODO? echo_fd = onoff * STDERR_FILENO;
+//TODO? echo_fd = xopen(arg, O_WRONLY|O_CREAT|O_TRUNC);
+ } else if (DIR_SAY == key) {
+ // just print argument verbatim
+ fprintf(stderr, arg);
+ }
+ // next, please!
+ argv++;
+ // ordinary expect-send pair!
+ } else {
+ //-----------------------
+ // do expect
+ //-----------------------
+ int expect_len;
+ size_t buf_len = 0;
+ size_t max_len = max_abort_len;
+
+ struct pollfd pfd;
+#if ENABLE_FEATURE_CHAT_NOFAIL
+ int nofail = 0;
+#endif
+ char *expect = *argv++;
+
+ // sanity check: shall we really expect something?
+ if (!expect)
+ goto expect_done;
+
+#if ENABLE_FEATURE_CHAT_NOFAIL
+ // if expect starts with -
+ if ('-' == *expect) {
+ // swallow -
+ expect++;
+ // and enter nofail mode
+ nofail++;
+ }
+#endif
+
+#ifdef ___TEST___BUF___ // test behaviour with a small buffer
+# undef COMMON_BUFSIZE
+# define COMMON_BUFSIZE 6
+#endif
+ // expand escape sequences in expect
+ expect_len = unescape(expect, &expect_len /*dummy*/);
+ if (expect_len > max_len)
+ max_len = expect_len;
+ // sanity check:
+ // we should expect more than nothing but not more than input buffer
+ // TODO: later we'll get rid of fixed-size buffer
+ if (!expect_len)
+ goto expect_done;
+ if (max_len >= COMMON_BUFSIZE) {
+ exitcode = ERR_MEM;
+ goto expect_done;
+ }
+
+ // get reply
+ pfd.fd = STDIN_FILENO;
+ pfd.events = POLLIN;
+ while (!exitcode
+ && poll(&pfd, 1, timeout) > 0
+ && (pfd.revents & POLLIN)
+ ) {
+#define buf bb_common_bufsiz1
+ llist_t *l;
+ ssize_t delta;
+
+ // read next char from device
+ if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
+ // dump device output if ECHO ON or RECORD fname
+//TODO? if (echo_fd > 0) {
+//TODO? full_write(echo_fd, buf+buf_len, 1);
+//TODO? }
+ if (echo > 0)
+ full_write(STDERR_FILENO, buf+buf_len, 1);
+ buf_len++;
+ // move input frame if we've reached higher bound
+ if (buf_len > COMMON_BUFSIZE) {
+ memmove(buf, buf+buf_len-max_len, max_len);
+ buf_len = max_len;
+ }
+ }
+ // N.B. rule of thumb: values being looked for can
+ // be found only at the end of input buffer
+ // this allows to get rid of strstr() and memmem()
+
+ // TODO: make expect and abort strings processed uniformly
+ // abort condition is met? -> bail out
+ for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
+ size_t len = strlen(l->data);
+ delta = buf_len-len;
+ if (delta >= 0 && !memcmp(buf+delta, l->data, len))
+ goto expect_done;
+ }
+ exitcode = ERR_OK;
+
+ // expected reply received? -> goto next command
+ delta = buf_len - expect_len;
+ if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
+ goto expect_done;
+#undef buf
+ }
+
+ // device timed out or unexpected reply received
+ exitcode = ERR_TIMEOUT;
+ expect_done:
+#if ENABLE_FEATURE_CHAT_NOFAIL
+ // on success and when in nofail mode
+ // we should skip following subsend-subexpect pairs
+ if (nofail) {
+ if (!exitcode) {
+ // find last send before non-dashed expect
+ while (*argv && argv[1] && '-' == argv[1][0])
+ argv += 2;
+ // skip the pair
+ // N.B. do we really need this?!
+ if (!*argv++ || !*argv++)
+ break;
+ }
+ // nofail mode also clears all but IO errors (or signals)
+ if (ERR_IO != exitcode)
+ exitcode = ERR_OK;
+ }
+#endif
+ // bail out unless we expected successfully
+ if (exitcode)
+ break;
+
+ //-----------------------
+ // do send
+ //-----------------------
+ if (*argv) {
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+ int nocr = 0; // inhibit terminating command with \r
+#endif
+ char *loaded = NULL; // loaded command
+ size_t len;
+ char *buf = *argv++;
+
+ // if command starts with @
+ // load "real" command from file named after @
+ if ('@' == *buf) {
+ // skip the @ and any following white-space
+ trim(++buf);
+ buf = loaded = xmalloc_xopen_read_close(buf, NULL);
+ }
+
+ // expand escape sequences in command
+ len = unescape(buf, &nocr);
+
+ // send command
+#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
+ pfd.fd = STDOUT_FILENO;
+ pfd.events = POLLOUT;
+ while (len && !exitcode
+ && poll(&pfd, 1, timeout) > 0
+ && (pfd.revents & POLLOUT)
+ ) {
+ // ugly! ugly! ugly!
+ // gotta send char by char to achieve this!
+ // Brrr...
+ // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
+ // "\\K" means send BREAK
+ char c = *buf;
+ if ('\\' == c) {
+ c = *++buf;
+ if ('d' == c) {
+ sleep(1);
+ len--;
+ continue;
+ } else if ('p' == c) {
+ usleep(10000);
+ len--;
+ continue;
+ } else if ('K' == c) {
+ tcsendbreak(STDOUT_FILENO, 0);
+ len--;
+ continue;
+ } else {
+ buf--;
+ }
+ }
+ if (safe_write(STDOUT_FILENO, buf, 1) > 0) {
+ len--;
+ buf++;
+ } else
+ break;
+ }
+#else
+// if (len) {
+ alarm(timeout);
+ len -= full_write(STDOUT_FILENO, buf, len);
+ alarm(0);
+// }
+#endif
+
+ // report I/O error if there still exists at least one non-sent char
+ if (len)
+ exitcode = ERR_IO;
+
+ // free loaded command (if any)
+ if (loaded)
+ free(loaded);
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+ // or terminate command with \r (if not inhibited)
+ else if (!nocr)
+ xwrite(STDOUT_FILENO, "\r", 1);
+#endif
+
+ // bail out unless we sent command successfully
+ if (exitcode)
+ break;
+
+ }
+ }
+ }
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+#endif
+
+ return exitcode;
+}
diff --git a/miscutils/chrt.c b/miscutils/chrt.c
new file mode 100644
index 0000000..cc5660b
--- /dev/null
+++ b/miscutils/chrt.c
@@ -0,0 +1,123 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chrt - manipulate real-time attributes of a process
+ * Copyright (c) 2006-2007 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include "libbb.h"
+#ifndef _POSIX_PRIORITY_SCHEDULING
+#warning your system may be foobared
+#endif
+static const struct {
+ int policy;
+ char name[12];
+} policies[] = {
+ {SCHED_OTHER, "SCHED_OTHER"},
+ {SCHED_FIFO, "SCHED_FIFO"},
+ {SCHED_RR, "SCHED_RR"}
+};
+
+static void show_min_max(int pol)
+{
+ const char *fmt = "%s min/max priority\t: %d/%d\n\0%s not supported?\n";
+ int max, min;
+ max = sched_get_priority_max(pol);
+ min = sched_get_priority_min(pol);
+ if (max >= 0 && min >= 0)
+ printf(fmt, policies[pol].name, min, max);
+ else {
+ fmt += 29;
+ printf(fmt, policies[pol].name);
+ }
+}
+
+#define OPT_m (1<<0)
+#define OPT_p (1<<1)
+#define OPT_r (1<<2)
+#define OPT_f (1<<3)
+#define OPT_o (1<<4)
+
+int chrt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chrt_main(int argc UNUSED_PARAM, char **argv)
+{
+ pid_t pid = 0;
+ unsigned opt;
+ struct sched_param sp;
+ char *pid_str;
+ char *priority = priority; /* for compiler */
+ const char *current_new;
+ int policy = SCHED_RR;
+
+ /* at least 1 arg; only one policy accepted */
+ opt_complementary = "-1:r--fo:f--ro:r--fo";
+ opt = getopt32(argv, "+mprfo");
+ if (opt & OPT_r)
+ policy = SCHED_RR;
+ if (opt & OPT_f)
+ policy = SCHED_FIFO;
+ if (opt & OPT_o)
+ policy = SCHED_OTHER;
+ if (opt & OPT_m) { /* print min/max */
+ show_min_max(SCHED_FIFO);
+ show_min_max(SCHED_RR);
+ show_min_max(SCHED_OTHER);
+ fflush_stdout_and_exit(EXIT_SUCCESS);
+ }
+
+ argv += optind;
+ if (opt & OPT_p) {
+ pid_str = *argv++;
+ if (*argv) { /* "-p <priority> <pid> [...]" */
+ priority = pid_str;
+ pid_str = *argv;
+ }
+ /* else "-p <pid>", and *argv == NULL */
+ pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1);
+ } else {
+ priority = *argv++;
+ if (!*argv)
+ bb_show_usage();
+ }
+
+ current_new = "current\0new";
+ if (opt & OPT_p) {
+ int pol;
+ print_rt_info:
+ pol = sched_getscheduler(pid);
+ if (pol < 0)
+ bb_perror_msg_and_die("can't %cet pid %d's policy", 'g', pid);
+ printf("pid %d's %s scheduling policy: %s\n",
+ pid, current_new, policies[pol].name);
+ if (sched_getparam(pid, &sp))
+ bb_perror_msg_and_die("can't get pid %d's attributes", pid);
+ printf("pid %d's %s scheduling priority: %d\n",
+ pid, current_new, sp.sched_priority);
+ if (!*argv) {
+ /* Either it was just "-p <pid>",
+ * or it was "-p <priority> <pid>" and we came here
+ * for the second time (see goto below) */
+ return EXIT_SUCCESS;
+ }
+ *argv = NULL;
+ current_new += 8;
+ }
+
+ /* from the manpage of sched_getscheduler:
+ [...] sched_priority can have a value in the range 0 to 99.
+ [...] SCHED_OTHER or SCHED_BATCH must be assigned static priority 0.
+ [...] SCHED_FIFO or SCHED_RR can have static priority in 1..99 range.
+ */
+ sp.sched_priority = xstrtou_range(priority, 0, policy != SCHED_OTHER ? 1 : 0, 99);
+
+ if (sched_setscheduler(pid, policy, &sp) < 0)
+ bb_perror_msg_and_die("can't %cet pid %d's policy", 's', pid);
+
+ if (!*argv) /* "-p <priority> <pid> [...]" */
+ goto print_rt_info;
+
+ BB_EXECVP(*argv, argv);
+ bb_simple_perror_msg_and_die(*argv);
+}
diff --git a/miscutils/crond.c b/miscutils/crond.c
new file mode 100644
index 0000000..732fbb1
--- /dev/null
+++ b/miscutils/crond.c
@@ -0,0 +1,935 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * crond -d[#] -c <crondir> -f -b
+ *
+ * run as root, but NOT setuid root
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * (version 2.3.2)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+/* glibc frees previous setenv'ed value when we do next setenv()
+ * of the same variable. uclibc does not do this! */
+#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
+#define SETENV_LEAKS 0
+#else
+#define SETENV_LEAKS 1
+#endif
+
+
+#ifndef CRONTABS
+#define CRONTABS "/var/spool/cron/crontabs"
+#endif
+#ifndef TMPDIR
+#define TMPDIR "/var/spool/cron"
+#endif
+#ifndef SENDMAIL
+#define SENDMAIL "sendmail"
+#endif
+#ifndef SENDMAIL_ARGS
+#define SENDMAIL_ARGS "-ti", "oem"
+#endif
+#ifndef CRONUPDATE
+#define CRONUPDATE "cron.update"
+#endif
+#ifndef MAXLINES
+#define MAXLINES 256 /* max lines in non-root crontabs */
+#endif
+
+
+typedef struct CronFile {
+ struct CronFile *cf_Next;
+ struct CronLine *cf_LineBase;
+ char *cf_User; /* username */
+ smallint cf_Ready; /* bool: one or more jobs ready */
+ smallint cf_Running; /* bool: one or more jobs running */
+ smallint cf_Deleted; /* marked for deletion, ignore */
+} CronFile;
+
+typedef struct CronLine {
+ struct CronLine *cl_Next;
+ char *cl_Shell; /* shell command */
+ pid_t cl_Pid; /* running pid, 0, or armed (-1) */
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+ int cl_MailPos; /* 'empty file' size */
+ smallint cl_MailFlag; /* running pid is for mail */
+ char *cl_MailTo; /* whom to mail results */
+#endif
+ /* ordered by size, not in natural order. makes code smaller: */
+ char cl_Dow[7]; /* 0-6, beginning sunday */
+ char cl_Mons[12]; /* 0-11 */
+ char cl_Hrs[24]; /* 0-23 */
+ char cl_Days[32]; /* 1-31 */
+ char cl_Mins[60]; /* 0-59 */
+} CronLine;
+
+
+#define DaemonUid 0
+
+
+enum {
+ OPT_l = (1 << 0),
+ OPT_L = (1 << 1),
+ OPT_f = (1 << 2),
+ OPT_b = (1 << 3),
+ OPT_S = (1 << 4),
+ OPT_c = (1 << 5),
+ OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
+};
+#if ENABLE_FEATURE_CROND_D
+#define DebugOpt (option_mask32 & OPT_d)
+#else
+#define DebugOpt 0
+#endif
+
+
+struct globals {
+ unsigned LogLevel; /* = 8; */
+ const char *LogFile;
+ const char *CDir; /* = CRONTABS; */
+ CronFile *FileBase;
+#if SETENV_LEAKS
+ char *env_var_user;
+ char *env_var_home;
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define LogLevel (G.LogLevel )
+#define LogFile (G.LogFile )
+#define CDir (G.CDir )
+#define FileBase (G.FileBase )
+#define env_var_user (G.env_var_user )
+#define env_var_home (G.env_var_home )
+#define INIT_G() do { \
+ LogLevel = 8; \
+ CDir = CRONTABS; \
+} while (0)
+
+
+static void CheckUpdates(void);
+static void SynchronizeDir(void);
+static int TestJobs(time_t t1, time_t t2);
+static void RunJobs(void);
+static int CheckJobs(void);
+static void RunJob(const char *user, CronLine *line);
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+static void EndJob(const char *user, CronLine *line);
+#else
+#define EndJob(user, line) ((line)->cl_Pid = 0)
+#endif
+static void DeleteFile(const char *userName);
+
+
+#define LVL5 "\x05"
+#define LVL7 "\x07"
+#define LVL8 "\x08"
+#define LVL9 "\x09"
+#define WARN9 "\x49"
+#define DIE9 "\xc9"
+/* level >= 20 is "error" */
+#define ERR20 "\x14"
+
+static void crondlog(const char *ctl, ...)
+{
+ va_list va;
+ int level = (ctl[0] & 0x1f);
+
+ va_start(va, ctl);
+ if (level >= (int)LogLevel) {
+ /* Debug mode: all to (non-redirected) stderr, */
+ /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
+ if (!DebugOpt && LogFile) {
+ /* Otherwise (log to file): we reopen log file at every write: */
+ int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (logfd >= 0)
+ xmove_fd(logfd, STDERR_FILENO);
+ }
+// TODO: ERR -> error, WARN -> warning, LVL -> info
+ bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
+ }
+ va_end(va);
+ if (ctl[0] & 0x80)
+ exit(20);
+}
+
+int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crond_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned opt;
+
+ INIT_G();
+
+ /* "-b after -f is ignored", and so on for every pair a-b */
+ opt_complementary = "f-b:b-f:S-L:L-S" USE_FEATURE_CROND_D(":d-l")
+ ":l+:d+"; /* -l and -d have numeric param */
+ opt = getopt32(argv, "l:L:fbSc:" USE_FEATURE_CROND_D("d:"),
+ &LogLevel, &LogFile, &CDir
+ USE_FEATURE_CROND_D(,&LogLevel));
+ /* both -d N and -l N set the same variable: LogLevel */
+
+ if (!(opt & OPT_f)) {
+ /* close stdin, stdout, stderr.
+ * close unused descriptors - don't need them. */
+ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+ }
+
+ if (!DebugOpt && LogFile == NULL) {
+ /* logging to syslog */
+ openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ xchdir(CDir);
+ //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
+ xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
+ crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel);
+ SynchronizeDir();
+
+ /* main loop - synchronize to 1 second after the minute, minimum sleep
+ * of 1 second. */
+ {
+ time_t t1 = time(NULL);
+ time_t t2;
+ long dt;
+ int rescan = 60;
+ int sleep_time = 60;
+
+ write_pidfile("/var/run/crond.pid");
+ for (;;) {
+ sleep((sleep_time + 1) - (time(NULL) % sleep_time));
+
+ t2 = time(NULL);
+ dt = (long)t2 - (long)t1;
+
+ /*
+ * The file 'cron.update' is checked to determine new cron
+ * jobs. The directory is rescanned once an hour to deal
+ * with any screwups.
+ *
+ * check for disparity. Disparities over an hour either way
+ * result in resynchronization. A reverse-indexed disparity
+ * less then an hour causes us to effectively sleep until we
+ * match the original time (i.e. no re-execution of jobs that
+ * have just been run). A forward-indexed disparity less then
+ * an hour causes intermediate jobs to be run, but only once
+ * in the worst case.
+ *
+ * when running jobs, the inequality used is greater but not
+ * equal to t1, and less then or equal to t2.
+ */
+ if (--rescan == 0) {
+ rescan = 60;
+ SynchronizeDir();
+ }
+ CheckUpdates();
+ if (DebugOpt)
+ crondlog(LVL5 "wakeup dt=%ld", dt);
+ if (dt < -60 * 60 || dt > 60 * 60) {
+ crondlog(WARN9 "time disparity of %d minutes detected", dt / 60);
+ } else if (dt > 0) {
+ TestJobs(t1, t2);
+ RunJobs();
+ sleep(5);
+ if (CheckJobs() > 0) {
+ sleep_time = 10;
+ } else {
+ sleep_time = 60;
+ }
+ }
+ t1 = t2;
+ }
+ }
+ return 0; /* not reached */
+}
+
+#if SETENV_LEAKS
+/* We set environment *before* vfork (because we want to use vfork),
+ * so we cannot use setenv() - repeated calls to setenv() may leak memory!
+ * Using putenv(), and freeing memory after unsetenv() won't leak */
+static void safe_setenv4(char **pvar_val, const char *var, const char *val /*, int len*/)
+{
+ const int len = 4; /* both var names are 4 char long */
+ char *var_val = *pvar_val;
+
+ if (var_val) {
+ var_val[len] = '\0'; /* nuke '=' */
+ unsetenv(var_val);
+ free(var_val);
+ }
+ *pvar_val = xasprintf("%s=%s", var, val);
+ putenv(*pvar_val);
+}
+#endif
+
+static void SetEnv(struct passwd *pas)
+{
+#if SETENV_LEAKS
+ safe_setenv4(&env_var_user, "USER", pas->pw_name);
+ safe_setenv4(&env_var_home, "HOME", pas->pw_dir);
+ /* if we want to set user's shell instead: */
+ /*safe_setenv(env_var_user, "SHELL", pas->pw_shell, 5);*/
+#else
+ xsetenv("USER", pas->pw_name);
+ xsetenv("HOME", pas->pw_dir);
+#endif
+ /* currently, we use constant one: */
+ /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
+}
+
+static void ChangeUser(struct passwd *pas)
+{
+ /* careful: we're after vfork! */
+ change_identity(pas); /* - initgroups, setgid, setuid */
+ if (chdir(pas->pw_dir) < 0) {
+ crondlog(LVL9 "can't chdir(%s)", pas->pw_dir);
+ if (chdir(TMPDIR) < 0) {
+ crondlog(DIE9 "can't chdir(%s)", TMPDIR); /* exits */
+ }
+ }
+}
+
+static const char DowAry[] ALIGN1 =
+ "sun""mon""tue""wed""thu""fri""sat"
+ /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
+;
+
+static const char MonAry[] ALIGN1 =
+ "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
+ /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
+;
+
+static void ParseField(char *user, char *ary, int modvalue, int off,
+ const char *names, char *ptr)
+/* 'names' is a pointer to a set of 3-char abbreviations */
+{
+ char *base = ptr;
+ int n1 = -1;
+ int n2 = -1;
+
+ // this can't happen due to config_read()
+ /*if (base == NULL)
+ return;*/
+
+ while (1) {
+ int skip = 0;
+
+ /* Handle numeric digit or symbol or '*' */
+ if (*ptr == '*') {
+ n1 = 0; /* everything will be filled */
+ n2 = modvalue - 1;
+ skip = 1;
+ ++ptr;
+ } else if (isdigit(*ptr)) {
+ if (n1 < 0) {
+ n1 = strtol(ptr, &ptr, 10) + off;
+ } else {
+ n2 = strtol(ptr, &ptr, 10) + off;
+ }
+ skip = 1;
+ } else if (names) {
+ int i;
+
+ for (i = 0; names[i]; i += 3) {
+ /* was using strncmp before... */
+ if (strncasecmp(ptr, &names[i], 3) == 0) {
+ ptr += 3;
+ if (n1 < 0) {
+ n1 = i / 3;
+ } else {
+ n2 = i / 3;
+ }
+ skip = 1;
+ break;
+ }
+ }
+ }
+
+ /* handle optional range '-' */
+ if (skip == 0) {
+ goto err;
+ }
+ if (*ptr == '-' && n2 < 0) {
+ ++ptr;
+ continue;
+ }
+
+ /*
+ * collapse single-value ranges, handle skipmark, and fill
+ * in the character array appropriately.
+ */
+ if (n2 < 0) {
+ n2 = n1;
+ }
+ if (*ptr == '/') {
+ skip = strtol(ptr + 1, &ptr, 10);
+ }
+
+ /*
+ * fill array, using a failsafe is the easiest way to prevent
+ * an endless loop
+ */
+ {
+ int s0 = 1;
+ int failsafe = 1024;
+
+ --n1;
+ do {
+ n1 = (n1 + 1) % modvalue;
+
+ if (--s0 == 0) {
+ ary[n1 % modvalue] = 1;
+ s0 = skip;
+ }
+ if (--failsafe == 0) {
+ goto err;
+ }
+ } while (n1 != n2);
+
+ }
+ if (*ptr != ',') {
+ break;
+ }
+ ++ptr;
+ n1 = -1;
+ n2 = -1;
+ }
+
+ if (*ptr) {
+ err:
+ crondlog(WARN9 "user %s: parse error at %s", user, base);
+ return;
+ }
+
+ if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */
+ /* can't use crondlog, it inserts '\n' */
+ int i;
+ for (i = 0; i < modvalue; ++i)
+ fprintf(stderr, "%d", (unsigned char)ary[i]);
+ fputc('\n', stderr);
+ }
+}
+
+static void FixDayDow(CronLine *line)
+{
+ unsigned i;
+ int weekUsed = 0;
+ int daysUsed = 0;
+
+ for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
+ if (line->cl_Dow[i] == 0) {
+ weekUsed = 1;
+ break;
+ }
+ }
+ for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
+ if (line->cl_Days[i] == 0) {
+ daysUsed = 1;
+ break;
+ }
+ }
+ if (weekUsed != daysUsed) {
+ if (weekUsed)
+ memset(line->cl_Days, 0, sizeof(line->cl_Days));
+ else /* daysUsed */
+ memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
+ }
+}
+
+static void SynchronizeFile(const char *fileName)
+{
+ struct parser_t *parser;
+ struct stat sbuf;
+ int maxLines;
+ char *tokens[6];
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+ char *mailTo = NULL;
+#endif
+
+ if (!fileName)
+ return;
+
+ DeleteFile(fileName);
+ parser = config_open(fileName);
+ if (!parser)
+ return;
+
+ maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
+
+ if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
+ CronFile *file = xzalloc(sizeof(CronFile));
+ CronLine **pline;
+ int n;
+
+ file->cf_User = xstrdup(fileName);
+ pline = &file->cf_LineBase;
+
+ while (1) {
+ CronLine *line;
+
+ if (!--maxLines)
+ break;
+ n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
+ if (!n)
+ break;
+
+ if (DebugOpt)
+ crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
+
+ /* check if line is setting MAILTO= */
+ if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+ free(mailTo);
+ mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
+#endif /* otherwise just ignore such lines */
+ continue;
+ }
+ /* check if a minimum of tokens is specified */
+ if (n < 6)
+ continue;
+ *pline = line = xzalloc(sizeof(*line));
+ /* parse date ranges */
+ ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]);
+ ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]);
+ ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]);
+ ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]);
+ ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]);
+ /*
+ * fix days and dow - if one is not "*" and the other
+ * is "*", the other is set to 0, and vise-versa
+ */
+ FixDayDow(line);
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+ /* copy mailto (can be NULL) */
+ line->cl_MailTo = xstrdup(mailTo);
+#endif
+ /* copy command */
+ line->cl_Shell = xstrdup(tokens[5]);
+ if (DebugOpt) {
+ crondlog(LVL5 " command:%s", tokens[5]);
+ }
+ pline = &line->cl_Next;
+//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
+ }
+ *pline = NULL;
+
+ file->cf_Next = FileBase;
+ FileBase = file;
+
+ if (maxLines == 0) {
+ crondlog(WARN9 "user %s: too many lines", fileName);
+ }
+ }
+ config_close(parser);
+}
+
+static void CheckUpdates(void)
+{
+ FILE *fi;
+ char buf[256];
+
+ fi = fopen_for_read(CRONUPDATE);
+ if (fi != NULL) {
+ unlink(CRONUPDATE);
+ while (fgets(buf, sizeof(buf), fi) != NULL) {
+ /* use first word only */
+ SynchronizeFile(strtok(buf, " \t\r\n"));
+ }
+ fclose(fi);
+ }
+}
+
+static void SynchronizeDir(void)
+{
+ CronFile *file;
+ /* Attempt to delete the database. */
+ again:
+ for (file = FileBase; file; file = file->cf_Next) {
+ if (!file->cf_Deleted) {
+ DeleteFile(file->cf_User);
+ goto again;
+ }
+ }
+
+ /*
+ * Remove cron update file
+ *
+ * Re-chdir, in case directory was renamed & deleted, or otherwise
+ * screwed up.
+ *
+ * scan directory and add associated users
+ */
+ unlink(CRONUPDATE);
+ if (chdir(CDir) < 0) {
+ crondlog(DIE9 "can't chdir(%s)", CDir);
+ }
+ {
+ DIR *dir = opendir(".");
+ struct dirent *den;
+
+ if (!dir)
+ crondlog(DIE9 "can't chdir(%s)", "."); /* exits */
+ while ((den = readdir(dir)) != NULL) {
+ if (strchr(den->d_name, '.') != NULL) {
+ continue;
+ }
+ if (getpwnam(den->d_name)) {
+ SynchronizeFile(den->d_name);
+ } else {
+ crondlog(LVL7 "ignoring %s", den->d_name);
+ }
+ }
+ closedir(dir);
+ }
+}
+
+/*
+ * DeleteFile() - delete user database
+ *
+ * Note: multiple entries for same user may exist if we were unable to
+ * completely delete a database due to running processes.
+ */
+static void DeleteFile(const char *userName)
+{
+ CronFile **pfile = &FileBase;
+ CronFile *file;
+
+ while ((file = *pfile) != NULL) {
+ if (strcmp(userName, file->cf_User) == 0) {
+ CronLine **pline = &file->cf_LineBase;
+ CronLine *line;
+
+ file->cf_Running = 0;
+ file->cf_Deleted = 1;
+
+ while ((line = *pline) != NULL) {
+ if (line->cl_Pid > 0) {
+ file->cf_Running = 1;
+ pline = &line->cl_Next;
+ } else {
+ *pline = line->cl_Next;
+ free(line->cl_Shell);
+ free(line);
+ }
+ }
+ if (file->cf_Running == 0) {
+ *pfile = file->cf_Next;
+ free(file->cf_User);
+ free(file);
+ } else {
+ pfile = &file->cf_Next;
+ }
+ } else {
+ pfile = &file->cf_Next;
+ }
+ }
+}
+
+/*
+ * TestJobs()
+ *
+ * determine which jobs need to be run. Under normal conditions, the
+ * period is about a minute (one scan). Worst case it will be one
+ * hour (60 scans).
+ */
+static int TestJobs(time_t t1, time_t t2)
+{
+ int nJobs = 0;
+ time_t t;
+
+ /* Find jobs > t1 and <= t2 */
+
+ for (t = t1 - t1 % 60; t <= t2; t += 60) {
+ struct tm *tp;
+ CronFile *file;
+ CronLine *line;
+
+ if (t <= t1)
+ continue;
+
+ tp = localtime(&t);
+ for (file = FileBase; file; file = file->cf_Next) {
+ if (DebugOpt)
+ crondlog(LVL5 "file %s:", file->cf_User);
+ if (file->cf_Deleted)
+ continue;
+ for (line = file->cf_LineBase; line; line = line->cl_Next) {
+ if (DebugOpt)
+ crondlog(LVL5 " line %s", line->cl_Shell);
+ if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour]
+ && (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
+ && line->cl_Mons[tp->tm_mon]
+ ) {
+ if (DebugOpt) {
+ crondlog(LVL5 " job: %d %s",
+ (int)line->cl_Pid, line->cl_Shell);
+ }
+ if (line->cl_Pid > 0) {
+ crondlog(LVL8 "user %s: process already running: %s",
+ file->cf_User, line->cl_Shell);
+ } else if (line->cl_Pid == 0) {
+ line->cl_Pid = -1;
+ file->cf_Ready = 1;
+ ++nJobs;
+ }
+ }
+ }
+ }
+ }
+ return nJobs;
+}
+
+static void RunJobs(void)
+{
+ CronFile *file;
+ CronLine *line;
+
+ for (file = FileBase; file; file = file->cf_Next) {
+ if (!file->cf_Ready)
+ continue;
+
+ file->cf_Ready = 0;
+ for (line = file->cf_LineBase; line; line = line->cl_Next) {
+ if (line->cl_Pid >= 0)
+ continue;
+
+ RunJob(file->cf_User, line);
+ crondlog(LVL8 "USER %s pid %3d cmd %s",
+ file->cf_User, (int)line->cl_Pid, line->cl_Shell);
+ if (line->cl_Pid < 0) {
+ file->cf_Ready = 1;
+ } else if (line->cl_Pid > 0) {
+ file->cf_Running = 1;
+ }
+ }
+ }
+}
+
+/*
+ * CheckJobs() - check for job completion
+ *
+ * Check for job completion, return number of jobs still running after
+ * all done.
+ */
+static int CheckJobs(void)
+{
+ CronFile *file;
+ CronLine *line;
+ int nStillRunning = 0;
+
+ for (file = FileBase; file; file = file->cf_Next) {
+ if (file->cf_Running) {
+ file->cf_Running = 0;
+
+ for (line = file->cf_LineBase; line; line = line->cl_Next) {
+ int status, r;
+ if (line->cl_Pid <= 0)
+ continue;
+
+ r = waitpid(line->cl_Pid, &status, WNOHANG);
+ if (r < 0 || r == line->cl_Pid) {
+ EndJob(file->cf_User, line);
+ if (line->cl_Pid) {
+ file->cf_Running = 1;
+ }
+ } else if (r == 0) {
+ file->cf_Running = 1;
+ }
+ }
+ }
+ nStillRunning += file->cf_Running;
+ }
+ return nStillRunning;
+}
+
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+
+// TODO: sendmail should be _run-time_ option, not compile-time!
+
+static void
+ForkJob(const char *user, CronLine *line, int mailFd,
+ const char *prog, const char *cmd, const char *arg,
+ const char *mail_filename)
+{
+ struct passwd *pas;
+ pid_t pid;
+
+ /* prepare things before vfork */
+ pas = getpwnam(user);
+ if (!pas) {
+ crondlog(LVL9 "can't get uid for %s", user);
+ goto err;
+ }
+ SetEnv(pas);
+
+ pid = vfork();
+ if (pid == 0) {
+ /* CHILD */
+ /* change running state to the user in question */
+ ChangeUser(pas);
+ if (DebugOpt) {
+ crondlog(LVL5 "child running %s", prog);
+ }
+ if (mailFd >= 0) {
+ xmove_fd(mailFd, mail_filename ? 1 : 0);
+ dup2(1, 2);
+ }
+ /* crond 3.0pl1-100 puts tasks in separate process groups */
+ bb_setpgrp();
+ execlp(prog, prog, cmd, arg, NULL);
+ crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
+ if (mail_filename) {
+ fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
+ }
+ _exit(EXIT_SUCCESS);
+ }
+
+ line->cl_Pid = pid;
+ if (pid < 0) {
+ /* FORK FAILED */
+ crondlog(ERR20 "can't vfork");
+ err:
+ line->cl_Pid = 0;
+ if (mail_filename) {
+ unlink(mail_filename);
+ }
+ } else if (mail_filename) {
+ /* PARENT, FORK SUCCESS
+ * rename mail-file based on pid of process
+ */
+ char mailFile2[128];
+
+ snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid);
+ rename(mail_filename, mailFile2); // TODO: xrename?
+ }
+
+ /*
+ * Close the mail file descriptor.. we can't just leave it open in
+ * a structure, closing it later, because we might run out of descriptors
+ */
+ if (mailFd >= 0) {
+ close(mailFd);
+ }
+}
+
+static void RunJob(const char *user, CronLine *line)
+{
+ char mailFile[128];
+ int mailFd = -1;
+
+ line->cl_Pid = 0;
+ line->cl_MailFlag = 0;
+
+ if (line->cl_MailTo) {
+ /* open mail file - owner root so nobody can screw with it. */
+ snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
+ mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
+
+ if (mailFd >= 0) {
+ line->cl_MailFlag = 1;
+ fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo,
+ line->cl_Shell);
+ line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
+ } else {
+ crondlog(ERR20 "cannot create mail file %s for user %s, "
+ "discarding output", mailFile, user);
+ }
+ }
+
+ ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
+}
+
+/*
+ * EndJob - called when job terminates and when mail terminates
+ */
+static void EndJob(const char *user, CronLine *line)
+{
+ int mailFd;
+ char mailFile[128];
+ struct stat sbuf;
+
+ /* No job */
+ if (line->cl_Pid <= 0) {
+ line->cl_Pid = 0;
+ return;
+ }
+
+ /*
+ * End of job and no mail file
+ * End of sendmail job
+ */
+ snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid);
+ line->cl_Pid = 0;
+
+ if (line->cl_MailFlag == 0) {
+ return;
+ }
+ line->cl_MailFlag = 0;
+
+ /*
+ * End of primary job - check for mail file. If size has increased and
+ * the file is still valid, we sendmail it.
+ */
+ mailFd = open(mailFile, O_RDONLY);
+ unlink(mailFile);
+ if (mailFd < 0) {
+ return;
+ }
+
+ if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
+ || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
+ || !S_ISREG(sbuf.st_mode)
+ ) {
+ close(mailFd);
+ return;
+ }
+ if (line->cl_MailTo)
+ ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
+}
+
+#else /* crond without sendmail */
+
+static void RunJob(const char *user, CronLine *line)
+{
+ struct passwd *pas;
+ pid_t pid;
+
+ /* prepare things before vfork */
+ pas = getpwnam(user);
+ if (!pas) {
+ crondlog(LVL9 "can't get uid for %s", user);
+ goto err;
+ }
+ SetEnv(pas);
+
+ /* fork as the user in question and run program */
+ pid = vfork();
+ if (pid == 0) {
+ /* CHILD */
+ /* change running state to the user in question */
+ ChangeUser(pas);
+ if (DebugOpt) {
+ crondlog(LVL5 "child running %s", DEFAULT_SHELL);
+ }
+ /* crond 3.0pl1-100 puts tasks in separate process groups */
+ bb_setpgrp();
+ execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
+ crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
+ DEFAULT_SHELL, "-c", line->cl_Shell);
+ _exit(EXIT_SUCCESS);
+ }
+ if (pid < 0) {
+ /* FORK FAILED */
+ crondlog(ERR20 "can't vfork");
+ err:
+ pid = 0;
+ }
+ line->cl_Pid = pid;
+}
+
+#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */
diff --git a/miscutils/crontab.c b/miscutils/crontab.c
new file mode 100644
index 0000000..673b558
--- /dev/null
+++ b/miscutils/crontab.c
@@ -0,0 +1,235 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * CRONTAB
+ *
+ * usually setuid root, -c option only works if getuid() == geteuid()
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#ifndef CRONTABS
+#define CRONTABS "/var/spool/cron/crontabs"
+#endif
+#ifndef CRONUPDATE
+#define CRONUPDATE "cron.update"
+#endif
+
+static void change_user(const struct passwd *pas)
+{
+ xsetenv("USER", pas->pw_name);
+ xsetenv("HOME", pas->pw_dir);
+ xsetenv("SHELL", DEFAULT_SHELL);
+
+ /* initgroups, setgid, setuid */
+ change_identity(pas);
+
+ if (chdir(pas->pw_dir) < 0) {
+ bb_perror_msg("chdir(%s) by %s failed",
+ pas->pw_dir, pas->pw_name);
+ xchdir("/tmp");
+ }
+}
+
+static void edit_file(const struct passwd *pas, const char *file)
+{
+ const char *ptr;
+ int pid = vfork();
+
+ if (pid < 0) /* failure */
+ bb_perror_msg_and_die("vfork");
+ if (pid) { /* parent */
+ wait4pid(pid);
+ return;
+ }
+
+ /* CHILD - change user and run editor */
+ change_user(pas);
+ ptr = getenv("VISUAL");
+ if (!ptr) {
+ ptr = getenv("EDITOR");
+ if (!ptr)
+ ptr = "vi";
+ }
+
+ BB_EXECLP(ptr, ptr, file, NULL);
+ bb_perror_msg_and_die("exec %s", ptr);
+}
+
+static int open_as_user(const struct passwd *pas, const char *file)
+{
+ pid_t pid;
+ char c;
+
+ pid = vfork();
+ if (pid < 0) /* ERROR */
+ bb_perror_msg_and_die("vfork");
+ if (pid) { /* PARENT */
+ if (wait4pid(pid) == 0) {
+ /* exitcode 0: child says it can read */
+ return open(file, O_RDONLY);
+ }
+ return -1;
+ }
+
+ /* CHILD */
+ /* initgroups, setgid, setuid */
+ change_identity(pas);
+ /* We just try to read one byte. If it works, file is readable
+ * under this user. We signal that by exiting with 0. */
+ _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0);
+}
+
+int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crontab_main(int argc UNUSED_PARAM, char **argv)
+{
+ const struct passwd *pas;
+ const char *crontab_dir = CRONTABS;
+ char *tmp_fname;
+ char *new_fname;
+ char *user_name; /* -u USER */
+ int fd;
+ int src_fd;
+ int opt_ler;
+
+ /* file [opts] Replace crontab from file
+ * - [opts] Replace crontab from stdin
+ * -u user User
+ * -c dir Crontab directory
+ * -l List crontab for user
+ * -e Edit crontab for user
+ * -r Delete crontab for user
+ * bbox also supports -d == -r, but most other crontab
+ * implementations do not. Deprecated.
+ */
+ enum {
+ OPT_u = (1 << 0),
+ OPT_c = (1 << 1),
+ OPT_l = (1 << 2),
+ OPT_e = (1 << 3),
+ OPT_r = (1 << 4),
+ OPT_ler = OPT_l + OPT_e + OPT_r,
+ };
+
+ opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
+ opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
+ argv += optind;
+
+ if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */
+ /* run by non-root? */
+ if (opt_ler & (OPT_u|OPT_c))
+ bb_error_msg_and_die("only root can use -c or -u");
+ }
+
+ if (opt_ler & OPT_u) {
+ pas = getpwnam(user_name);
+ if (!pas)
+ bb_error_msg_and_die("user %s is not known", user_name);
+ } else {
+/* XXX: xgetpwuid */
+ uid_t my_uid = getuid();
+ pas = getpwuid(my_uid);
+ if (!pas)
+ bb_perror_msg_and_die("unknown uid %d", (int)my_uid);
+ }
+
+#define user_name DONT_USE_ME_BEYOND_THIS_POINT
+
+ /* From now on, keep only -l, -e, -r bits */
+ opt_ler &= OPT_ler;
+ if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
+ bb_show_usage();
+
+ /* Read replacement file under user's UID/GID/group vector */
+ src_fd = STDIN_FILENO;
+ if (!opt_ler) { /* Replace? */
+ if (!argv[0])
+ bb_show_usage();
+ if (NOT_LONE_DASH(argv[0])) {
+ src_fd = open_as_user(pas, argv[0]);
+ if (src_fd < 0)
+ bb_error_msg_and_die("user %s cannot read %s",
+ pas->pw_name, argv[0]);
+ }
+ }
+
+ /* cd to our crontab directory */
+ xchdir(crontab_dir);
+
+ tmp_fname = NULL;
+
+ /* Handle requested operation */
+ switch (opt_ler) {
+
+ default: /* case OPT_r: Delete */
+ unlink(pas->pw_name);
+ break;
+
+ case OPT_l: /* List */
+ {
+ char *args[2] = { pas->pw_name, NULL };
+ return bb_cat(args);
+ /* list exits,
+ * the rest go play with cron update file */
+ }
+
+ case OPT_e: /* Edit */
+ tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid());
+ /* No O_EXCL: we don't want to be stuck if earlier crontabs
+ * were killed, leaving stale temp file behind */
+ src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ fchown(src_fd, pas->pw_uid, pas->pw_gid);
+ fd = open(pas->pw_name, O_RDONLY);
+ if (fd >= 0) {
+ bb_copyfd_eof(fd, src_fd);
+ close(fd);
+ xlseek(src_fd, 0, SEEK_SET);
+ }
+ close_on_exec_on(src_fd); /* don't want editor to see this fd */
+ edit_file(pas, tmp_fname);
+ /* fall through */
+
+ case 0: /* Replace (no -l, -e, or -r were given) */
+ new_fname = xasprintf("%s.new", pas->pw_name);
+ fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
+ if (fd >= 0) {
+ bb_copyfd_eof(src_fd, fd);
+ close(fd);
+ xrename(new_fname, pas->pw_name);
+ } else {
+ bb_error_msg("cannot create %s/%s",
+ crontab_dir, new_fname);
+ }
+ if (tmp_fname)
+ unlink(tmp_fname);
+ /*free(tmp_fname);*/
+ /*free(new_fname);*/
+
+ } /* switch */
+
+ /* Bump notification file. Handle window where crond picks file up
+ * before we can write our entry out.
+ */
+ while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
+ struct stat st;
+
+ fdprintf(fd, "%s\n", pas->pw_name);
+ if (fstat(fd, &st) != 0 || st.st_nlink != 0) {
+ /*close(fd);*/
+ break;
+ }
+ /* st.st_nlink == 0:
+ * file was deleted, maybe crond missed our notification */
+ close(fd);
+ /* loop */
+ }
+ if (fd < 0) {
+ bb_error_msg("cannot append to %s/%s",
+ crontab_dir, CRONUPDATE);
+ }
+ return 0;
+}
diff --git a/miscutils/dc.c b/miscutils/dc.c
new file mode 100644
index 0000000..ff2bc3b
--- /dev/null
+++ b/miscutils/dc.c
@@ -0,0 +1,256 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <math.h>
+
+/* Tiny RPN calculator, because "expr" didn't give me bitwise operations. */
+
+
+struct globals {
+ unsigned pointer;
+ unsigned base;
+ double stack[1];
+};
+enum { STACK_SIZE = (COMMON_BUFSIZE - offsetof(struct globals, stack)) / sizeof(double) };
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define pointer (G.pointer )
+#define base (G.base )
+#define stack (G.stack )
+#define INIT_G() do { \
+ base = 10; \
+} while (0)
+
+
+static void push(double a)
+{
+ if (pointer >= STACK_SIZE)
+ bb_error_msg_and_die("stack overflow");
+ stack[pointer++] = a;
+}
+
+static double pop(void)
+{
+ if (pointer == 0)
+ bb_error_msg_and_die("stack underflow");
+ return stack[--pointer];
+}
+
+static void add(void)
+{
+ push(pop() + pop());
+}
+
+static void sub(void)
+{
+ double subtrahend = pop();
+
+ push(pop() - subtrahend);
+}
+
+static void mul(void)
+{
+ push(pop() * pop());
+}
+
+#if ENABLE_FEATURE_DC_LIBM
+static void power(void)
+{
+ double topower = pop();
+
+ push(pow(pop(), topower));
+}
+#endif
+
+static void divide(void)
+{
+ double divisor = pop();
+
+ push(pop() / divisor);
+}
+
+static void mod(void)
+{
+ unsigned d = pop();
+
+ push((unsigned) pop() % d);
+}
+
+static void and(void)
+{
+ push((unsigned) pop() & (unsigned) pop());
+}
+
+static void or(void)
+{
+ push((unsigned) pop() | (unsigned) pop());
+}
+
+static void eor(void)
+{
+ push((unsigned) pop() ^ (unsigned) pop());
+}
+
+static void not(void)
+{
+ push(~(unsigned) pop());
+}
+
+static void set_output_base(void)
+{
+ static const char bases[] ALIGN1 = { 2, 8, 10, 16, 0 };
+ unsigned b = (unsigned)pop();
+
+ base = *strchrnul(bases, b);
+ if (base == 0) {
+ bb_error_msg("error, base %u is not supported", b);
+ base = 10;
+ }
+}
+
+static void print_base(double print)
+{
+ unsigned x, i;
+
+ if (base == 10) {
+ printf("%g\n", print);
+ return;
+ }
+
+ x = (unsigned)print;
+ switch (base) {
+ case 16:
+ printf("%x\n", x);
+ break;
+ case 8:
+ printf("%o\n", x);
+ break;
+ default: /* base 2 */
+ i = (unsigned)INT_MAX + 1;
+ do {
+ if (x & i) break;
+ i >>= 1;
+ } while (i > 1);
+ do {
+ bb_putchar('1' - !(x & i));
+ i >>= 1;
+ } while (i);
+ bb_putchar('\n');
+ }
+}
+
+static void print_stack_no_pop(void)
+{
+ unsigned i = pointer;
+ while (i)
+ print_base(stack[--i]);
+}
+
+static void print_no_pop(void)
+{
+ print_base(stack[pointer-1]);
+}
+
+struct op {
+ const char name[4];
+ void (*function) (void);
+};
+
+static const struct op operators[] = {
+ {"+", add},
+ {"add", add},
+ {"-", sub},
+ {"sub", sub},
+ {"*", mul},
+ {"mul", mul},
+ {"/", divide},
+ {"div", divide},
+#if ENABLE_FEATURE_DC_LIBM
+ {"**", power},
+ {"exp", power},
+ {"pow", power},
+#endif
+ {"%", mod},
+ {"mod", mod},
+ {"and", and},
+ {"or", or},
+ {"not", not},
+ {"eor", eor},
+ {"xor", eor},
+ {"p", print_no_pop},
+ {"f", print_stack_no_pop},
+ {"o", set_output_base},
+ { /* zero filled */ }
+};
+
+static void stack_machine(const char *argument)
+{
+ char *endPointer;
+ double d;
+ const struct op *o = operators;
+
+ if (argument == 0)
+ return;
+
+ d = strtod(argument, &endPointer);
+
+ if (endPointer != argument) {
+ push(d);
+ return;
+ }
+
+ while (o->name[0]) {
+ if (strcmp(o->name, argument) == 0) {
+ o->function();
+ return;
+ }
+ o++;
+ }
+ bb_error_msg_and_die("%s: syntax error", argument);
+}
+
+/* return pointer to next token in buffer and set *buffer to one char
+ * past the end of the above mentioned token
+ */
+static char *get_token(char **buffer)
+{
+ char *current = skip_whitespace(*buffer);
+ if (*current != '\0') {
+ *buffer = skip_non_whitespace(current);
+ return current;
+ }
+ return NULL;
+}
+
+int dc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dc_main(int argc UNUSED_PARAM, char **argv)
+{
+ INIT_G();
+
+ argv++;
+ if (!argv[0]) {
+ /* take stuff from stdin if no args are given */
+ char *line;
+ char *cursor;
+ char *token;
+ while ((line = xmalloc_fgetline(stdin)) != NULL) {
+ cursor = line;
+ while (1) {
+ token = get_token(&cursor);
+ if (!token) break;
+ *cursor++ = '\0';
+ stack_machine(token);
+ }
+ free(line);
+ }
+ } else {
+ if (argv[0][0] == '-')
+ bb_show_usage();
+ do {
+ stack_machine(*argv);
+ } while (*++argv);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/devfsd.c b/miscutils/devfsd.c
new file mode 100644
index 0000000..61b97dc
--- /dev/null
+++ b/miscutils/devfsd.c
@@ -0,0 +1,1801 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/*
+ devfsd implementation for busybox
+
+ Copyright (C) 2003 by Tito Ragusa <farmatito@tiscali.it>
+
+ Busybox version is based on some previous work and ideas
+ Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it>
+
+ devfsd.c
+
+ Main file for devfsd (devfs daemon for Linux).
+
+ Copyright (C) 1998-2002 Richard Gooch
+
+ devfsd.h
+
+ Header file for devfsd (devfs daemon for Linux).
+
+ Copyright (C) 1998-2000 Richard Gooch
+
+ compat_name.c
+
+ Compatibility name file for devfsd (build compatibility names).
+
+ Copyright (C) 1998-2002 Richard Gooch
+
+ expression.c
+
+ This code provides Borne Shell-like expression expansion.
+
+ Copyright (C) 1997-1999 Richard Gooch
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Richard Gooch may be reached by email at rgooch@atnf.csiro.au
+ The postal address is:
+ Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
+*/
+#include "libbb.h"
+#include "xregex.h"
+#include <syslog.h>
+
+#include <sys/un.h>
+#include <sys/sysmacros.h>
+
+/* Various defines taken from linux/major.h */
+#define IDE0_MAJOR 3
+#define IDE1_MAJOR 22
+#define IDE2_MAJOR 33
+#define IDE3_MAJOR 34
+#define IDE4_MAJOR 56
+#define IDE5_MAJOR 57
+#define IDE6_MAJOR 88
+#define IDE7_MAJOR 89
+#define IDE8_MAJOR 90
+#define IDE9_MAJOR 91
+
+
+/* Various defines taken from linux/devfs_fs.h */
+#define DEVFSD_PROTOCOL_REVISION_KERNEL 5
+#define DEVFSD_IOCTL_BASE 'd'
+/* These are the various ioctls */
+#define DEVFSDIOC_GET_PROTO_REV _IOR(DEVFSD_IOCTL_BASE, 0, int)
+#define DEVFSDIOC_SET_EVENT_MASK _IOW(DEVFSD_IOCTL_BASE, 2, int)
+#define DEVFSDIOC_RELEASE_EVENT_QUEUE _IOW(DEVFSD_IOCTL_BASE, 3, int)
+#define DEVFSDIOC_SET_CONFIG_DEBUG_MASK _IOW(DEVFSD_IOCTL_BASE, 4, int)
+#define DEVFSD_NOTIFY_REGISTERED 0
+#define DEVFSD_NOTIFY_UNREGISTERED 1
+#define DEVFSD_NOTIFY_ASYNC_OPEN 2
+#define DEVFSD_NOTIFY_CLOSE 3
+#define DEVFSD_NOTIFY_LOOKUP 4
+#define DEVFSD_NOTIFY_CHANGE 5
+#define DEVFSD_NOTIFY_CREATE 6
+#define DEVFSD_NOTIFY_DELETE 7
+#define DEVFS_PATHLEN 1024
+/* Never change this otherwise the binary interface will change */
+
+struct devfsd_notify_struct
+{ /* Use native C types to ensure same types in kernel and user space */
+ unsigned int type; /* DEVFSD_NOTIFY_* value */
+ unsigned int mode; /* Mode of the inode or device entry */
+ unsigned int major; /* Major number of device entry */
+ unsigned int minor; /* Minor number of device entry */
+ unsigned int uid; /* Uid of process, inode or device entry */
+ unsigned int gid; /* Gid of process, inode or device entry */
+ unsigned int overrun_count; /* Number of lost events */
+ unsigned int namelen; /* Number of characters not including '\0' */
+ /* The device name MUST come last */
+ char devname[DEVFS_PATHLEN]; /* This will be '\0' terminated */
+};
+
+#define BUFFER_SIZE 16384
+#define DEVFSD_VERSION "1.3.25"
+#define CONFIG_FILE "/etc/devfsd.conf"
+#define MODPROBE "/sbin/modprobe"
+#define MODPROBE_SWITCH_1 "-k"
+#define MODPROBE_SWITCH_2 "-C"
+#define CONFIG_MODULES_DEVFS "/etc/modules.devfs"
+#define MAX_ARGS (6 + 1)
+#define MAX_SUBEXPR 10
+#define STRING_LENGTH 255
+
+/* for get_uid_gid() */
+#define UID 0
+#define GID 1
+
+/* fork_and_execute() */
+# define DIE 1
+# define NO_DIE 0
+
+/* for dir_operation() */
+#define RESTORE 0
+#define SERVICE 1
+#define READ_CONFIG 2
+
+/* Update only after changing code to reflect new protocol */
+#define DEVFSD_PROTOCOL_REVISION_DAEMON 5
+
+/* Compile-time check */
+#if DEVFSD_PROTOCOL_REVISION_KERNEL != DEVFSD_PROTOCOL_REVISION_DAEMON
+#error protocol version mismatch. Update your kernel headers
+#endif
+
+#define AC_PERMISSIONS 0
+#define AC_MODLOAD 1
+#define AC_EXECUTE 2
+#define AC_MFUNCTION 3 /* not supported by busybox */
+#define AC_CFUNCTION 4 /* not supported by busybox */
+#define AC_COPY 5
+#define AC_IGNORE 6
+#define AC_MKOLDCOMPAT 7
+#define AC_MKNEWCOMPAT 8
+#define AC_RMOLDCOMPAT 9
+#define AC_RMNEWCOMPAT 10
+#define AC_RESTORE 11
+
+struct permissions_type
+{
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+};
+
+struct execute_type
+{
+ char *argv[MAX_ARGS + 1]; /* argv[0] must always be the programme */
+};
+
+struct copy_type
+{
+ const char *source;
+ const char *destination;
+};
+
+struct action_type
+{
+ unsigned int what;
+ unsigned int when;
+};
+
+struct config_entry_struct
+{
+ struct action_type action;
+ regex_t preg;
+ union
+ {
+ struct permissions_type permissions;
+ struct execute_type execute;
+ struct copy_type copy;
+ }
+ u;
+ struct config_entry_struct *next;
+};
+
+struct get_variable_info
+{
+ const struct devfsd_notify_struct *info;
+ const char *devname;
+ char devpath[STRING_LENGTH];
+};
+
+static void dir_operation(int , const char * , int, unsigned long*);
+static void service(struct stat statbuf, char *path);
+static int st_expr_expand(char *, unsigned, const char *, const char *(*)(const char *, void *), void *);
+static const char *get_old_name(const char *, unsigned, char *, unsigned, unsigned);
+static int mksymlink(const char *oldpath, const char *newpath);
+static void read_config_file(char *path, int optional, unsigned long *event_mask);
+static void process_config_line(const char *, unsigned long *);
+static int do_servicing(int, unsigned long);
+static void service_name(const struct devfsd_notify_struct *);
+static void action_permissions(const struct devfsd_notify_struct *, const struct config_entry_struct *);
+static void action_execute(const struct devfsd_notify_struct *, const struct config_entry_struct *,
+ const regmatch_t *, unsigned);
+static void action_modload(const struct devfsd_notify_struct *info, const struct config_entry_struct *entry);
+static void action_copy(const struct devfsd_notify_struct *, const struct config_entry_struct *,
+ const regmatch_t *, unsigned);
+static void action_compat(const struct devfsd_notify_struct *, unsigned);
+static void free_config(void);
+static void restore(char *spath, struct stat source_stat, int rootlen);
+static int copy_inode(const char *, const struct stat *, mode_t, const char *, const struct stat *);
+static mode_t get_mode(const char *);
+static void signal_handler(int);
+static const char *get_variable(const char *, void *);
+static int make_dir_tree(const char *);
+static int expand_expression(char *, unsigned, const char *, const char *(*)(const char *, void *), void *,
+ const char *, const regmatch_t *, unsigned);
+static void expand_regexp(char *, size_t, const char *, const char *, const regmatch_t *, unsigned);
+static const char *expand_variable( char *, unsigned, unsigned *, const char *,
+ const char *(*)(const char *, void *), void *);
+static const char *get_variable_v2(const char *, const char *(*)(const char *, void *), void *);
+static char get_old_ide_name(unsigned , unsigned);
+static char *write_old_sd_name(char *, unsigned, unsigned, const char *);
+
+/* busybox functions */
+static int get_uid_gid(int flag, const char *string);
+static void safe_memcpy(char * dest, const char * src, int len);
+static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, const char *ptr);
+static unsigned int scan_dev_name(const char *d, unsigned int n, const char *ptr);
+
+/* Structs and vars */
+static struct config_entry_struct *first_config = NULL;
+static struct config_entry_struct *last_config = NULL;
+static char *mount_point = NULL;
+static volatile int caught_signal = FALSE;
+static volatile int caught_sighup = FALSE;
+static struct initial_symlink_struct {
+ const char *dest;
+ const char *name;
+} initial_symlinks[] = {
+ {"/proc/self/fd", "fd"},
+ {"fd/0", "stdin"},
+ {"fd/1", "stdout"},
+ {"fd/2", "stderr"},
+ {NULL, NULL},
+};
+
+static struct event_type {
+ unsigned int type; /* The DEVFSD_NOTIFY_* value */
+ const char *config_name; /* The name used in the config file */
+} event_types[] = {
+ {DEVFSD_NOTIFY_REGISTERED, "REGISTER"},
+ {DEVFSD_NOTIFY_UNREGISTERED, "UNREGISTER"},
+ {DEVFSD_NOTIFY_ASYNC_OPEN, "ASYNC_OPEN"},
+ {DEVFSD_NOTIFY_CLOSE, "CLOSE"},
+ {DEVFSD_NOTIFY_LOOKUP, "LOOKUP"},
+ {DEVFSD_NOTIFY_CHANGE, "CHANGE"},
+ {DEVFSD_NOTIFY_CREATE, "CREATE"},
+ {DEVFSD_NOTIFY_DELETE, "DELETE"},
+ {0xffffffff, NULL}
+};
+
+/* Busybox messages */
+
+static const char bb_msg_proto_rev[] ALIGN1 = "protocol revision";
+static const char bb_msg_bad_config[] ALIGN1 = "bad %s config file: %s";
+static const char bb_msg_small_buffer[] ALIGN1 = "buffer too small";
+static const char bb_msg_variable_not_found[] ALIGN1 = "variable: %s not found";
+
+/* Busybox stuff */
+#if ENABLE_DEVFSD_VERBOSE || ENABLE_DEBUG
+#define info_logger(p, fmt, args...) bb_info_msg(fmt, ## args)
+#define msg_logger(p, fmt, args...) bb_error_msg(fmt, ## args)
+#define msg_logger_and_die(p, fmt, args...) bb_error_msg_and_die(fmt, ## args)
+#define error_logger(p, fmt, args...) bb_perror_msg(fmt, ## args)
+#define error_logger_and_die(p, fmt, args...) bb_perror_msg_and_die(fmt, ## args)
+#else
+#define info_logger(p, fmt, args...)
+#define msg_logger(p, fmt, args...)
+#define msg_logger_and_die(p, fmt, args...) exit(EXIT_FAILURE)
+#define error_logger(p, fmt, args...)
+#define error_logger_and_die(p, fmt, args...) exit(EXIT_FAILURE)
+#endif
+
+static void safe_memcpy(char *dest, const char *src, int len)
+{
+ memcpy(dest , src, len);
+ dest[len] = '\0';
+}
+
+static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, const char *ptr)
+{
+ if (d[n - 4] == 'd' && d[n - 3] == 'i' && d[n - 2] == 's' && d[n - 1] == 'c')
+ return 2 + addendum;
+ if (d[n - 2] == 'c' && d[n - 1] == 'd')
+ return 3 + addendum;
+ if (ptr[0] == 'p' && ptr[1] == 'a' && ptr[2] == 'r' && ptr[3] == 't')
+ return 4 + addendum;
+ if (ptr[n - 2] == 'm' && ptr[n - 1] == 't')
+ return 5 + addendum;
+ return 0;
+}
+
+static unsigned int scan_dev_name(const char *d, unsigned int n, const char *ptr)
+{
+ if (d[0] == 's' && d[1] == 'c' && d[2] == 's' && d[3] == 'i' && d[4] == '/') {
+ if (d[n - 7] == 'g' && d[n - 6] == 'e' && d[n - 5] == 'n'
+ && d[n - 4] == 'e' && d[n - 3] == 'r' && d[n - 2] == 'i' && d[n - 1] == 'c'
+ )
+ return 1;
+ return scan_dev_name_common(d, n, 0, ptr);
+ }
+ if (d[0] == 'i' && d[1] == 'd' && d[2] == 'e' && d[3] == '/'
+ && d[4] == 'h' && d[5] == 'o' && d[6] == 's' && d[7] == 't'
+ )
+ return scan_dev_name_common(d, n, 4, ptr);
+ if (d[0] == 's' && d[1] == 'b' && d[2] == 'p' && d[3] == '/')
+ return 10;
+ if (d[0] == 'v' && d[1] == 'c' && d[2] == 'c' && d[3] == '/')
+ return 11;
+ if (d[0] == 'p' && d[1] == 't' && d[2] == 'y' && d[3] == '/')
+ return 12;
+ return 0;
+}
+
+/* Public functions follow */
+
+int devfsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int devfsd_main(int argc, char **argv)
+{
+ int print_version = FALSE;
+ int do_daemon = TRUE;
+ int no_polling = FALSE;
+ int do_scan;
+ int fd, proto_rev, count;
+ unsigned long event_mask = 0;
+ struct sigaction new_action;
+ struct initial_symlink_struct *curr;
+
+ if (argc < 2)
+ bb_show_usage();
+
+ for (count = 2; count < argc; ++count) {
+ if (argv[count][0] == '-') {
+ if (argv[count][1] == 'v' && !argv[count][2]) /* -v */
+ print_version = TRUE;
+ else if (ENABLE_DEVFSD_FG_NP && argv[count][1] == 'f'
+ && argv[count][2] == 'g' && !argv[count][3]) /* -fg */
+ do_daemon = FALSE;
+ else if (ENABLE_DEVFSD_FG_NP && argv[count][1] == 'n'
+ && argv[count][2] == 'p' && !argv[count][3]) /* -np */
+ no_polling = TRUE;
+ else
+ bb_show_usage();
+ }
+ }
+
+ mount_point = bb_simplify_path(argv[1]);
+
+ xchdir(mount_point);
+
+ fd = xopen(".devfsd", O_RDONLY);
+ close_on_exec_on(fd);
+ xioctl(fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev);
+
+ /*setup initial entries */
+ for (curr = initial_symlinks; curr->dest != NULL; ++curr)
+ symlink(curr->dest, curr->name);
+
+ /* NB: The check for CONFIG_FILE is done in read_config_file() */
+
+ if (print_version || (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev)) {
+ printf("%s v%s\nDaemon %s:\t%d\nKernel-side %s:\t%d\n",
+ applet_name, DEVFSD_VERSION, bb_msg_proto_rev,
+ DEVFSD_PROTOCOL_REVISION_DAEMON, bb_msg_proto_rev, proto_rev);
+ if (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev)
+ bb_error_msg_and_die("%s mismatch!", bb_msg_proto_rev);
+ exit(EXIT_SUCCESS); /* -v */
+ }
+ /* Tell kernel we are special(i.e. we get to see hidden entries) */
+ xioctl(fd, DEVFSDIOC_SET_EVENT_MASK, 0);
+
+ /* Set up SIGHUP and SIGUSR1 handlers */
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = 0;
+ new_action.sa_handler = signal_handler;
+ sigaction_set(SIGHUP, &new_action);
+ sigaction_set(SIGUSR1, &new_action);
+
+ printf("%s v%s started for %s\n", applet_name, DEVFSD_VERSION, mount_point);
+
+ /* Set umask so that mknod(2), open(2) and mkdir(2) have complete control over permissions */
+ umask(0);
+ read_config_file((char*)CONFIG_FILE, FALSE, &event_mask);
+ /* Do the scan before forking, so that boot scripts see the finished product */
+ dir_operation(SERVICE, mount_point, 0, NULL);
+
+ if (ENABLE_DEVFSD_FG_NP && no_polling)
+ exit(EXIT_SUCCESS);
+
+ if (ENABLE_DEVFSD_VERBOSE || ENABLE_DEBUG)
+ logmode = LOGMODE_BOTH;
+ else if (do_daemon == TRUE)
+ logmode = LOGMODE_SYSLOG;
+ /* This is the default */
+ /*else
+ logmode = LOGMODE_STDIO; */
+
+ if (do_daemon) {
+ /* Release so that the child can grab it */
+ xioctl(fd, DEVFSDIOC_RELEASE_EVENT_QUEUE, 0);
+ bb_daemonize_or_rexec(0, argv);
+ } else if (ENABLE_DEVFSD_FG_NP) {
+ setpgid(0, 0); /* Become process group leader */
+ }
+
+ while (TRUE) {
+ do_scan = do_servicing(fd, event_mask);
+
+ free_config();
+ read_config_file((char*)CONFIG_FILE, FALSE, &event_mask);
+ if (do_scan)
+ dir_operation(SERVICE, mount_point, 0, NULL);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP) free(mount_point);
+} /* End Function main */
+
+
+/* Private functions follow */
+
+static void read_config_file(char *path, int optional, unsigned long *event_mask)
+/* [SUMMARY] Read a configuration database.
+ <path> The path to read the database from. If this is a directory, all
+ entries in that directory will be read(except hidden entries).
+ <optional> If TRUE, the routine will silently ignore a missing config file.
+ <event_mask> The event mask is written here. This is not initialised.
+ [RETURNS] Nothing.
+*/
+{
+ struct stat statbuf;
+ FILE *fp;
+ char buf[STRING_LENGTH];
+ char *line = NULL;
+ char *p;
+
+ if (stat(path, &statbuf) == 0) {
+ /* Don't read 0 length files: ignored */
+ /*if (statbuf.st_size == 0)
+ return;*/
+ if (S_ISDIR(statbuf.st_mode)) {
+ p = bb_simplify_path(path);
+ dir_operation(READ_CONFIG, p, 0, event_mask);
+ free(p);
+ return;
+ }
+ fp = fopen_for_read(path);
+ if (fp != NULL) {
+ while (fgets(buf, STRING_LENGTH, fp) != NULL) {
+ /* Skip whitespace */
+ line = buf;
+ line = skip_whitespace(line);
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ process_config_line(line, event_mask);
+ }
+ fclose(fp);
+ } else {
+ goto read_config_file_err;
+ }
+ } else {
+read_config_file_err:
+ if (optional == 0 && errno == ENOENT)
+ error_logger_and_die(LOG_ERR, "read config file: %s", path);
+ }
+} /* End Function read_config_file */
+
+static void process_config_line(const char *line, unsigned long *event_mask)
+/* [SUMMARY] Process a line from a configuration file.
+ <line> The configuration line.
+ <event_mask> The event mask is written here. This is not initialised.
+ [RETURNS] Nothing.
+*/
+{
+ int num_args, count;
+ struct config_entry_struct *new;
+ char p[MAX_ARGS][STRING_LENGTH];
+ char when[STRING_LENGTH], what[STRING_LENGTH];
+ char name[STRING_LENGTH];
+ const char *msg = "";
+ char *ptr;
+ int i;
+
+ /* !!!! Only Uppercase Keywords in devsfd.conf */
+ static const char options[] ALIGN1 =
+ "CLEAR_CONFIG\0""INCLUDE\0""OPTIONAL_INCLUDE\0"
+ "RESTORE\0""PERMISSIONS\0""MODLOAD\0""EXECUTE\0"
+ "COPY\0""IGNORE\0""MKOLDCOMPAT\0""MKNEWCOMPAT\0"
+ "RMOLDCOMPAT\0""RMNEWCOMPAT\0";
+
+ for (count = 0; count < MAX_ARGS; ++count)
+ p[count][0] = '\0';
+ num_args = sscanf(line, "%s %s %s %s %s %s %s %s %s %s",
+ when, name, what,
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
+
+ i = index_in_strings(options, when);
+
+ /* "CLEAR_CONFIG" */
+ if (i == 0) {
+ free_config();
+ *event_mask = 0;
+ return;
+ }
+
+ if (num_args < 2)
+ goto process_config_line_err;
+
+ /* "INCLUDE" & "OPTIONAL_INCLUDE" */
+ if (i == 1 || i == 2) {
+ st_expr_expand(name, STRING_LENGTH, name, get_variable, NULL);
+ info_logger(LOG_INFO, "%sinclude: %s", (toupper(when[0]) == 'I') ? "": "optional_", name);
+ read_config_file(name, (toupper(when[0]) == 'I') ? FALSE : TRUE, event_mask);
+ return;
+ }
+ /* "RESTORE" */
+ if (i == 3) {
+ dir_operation(RESTORE, name, strlen(name),NULL);
+ return;
+ }
+ if (num_args < 3)
+ goto process_config_line_err;
+
+ new = xzalloc(sizeof *new);
+
+ for (count = 0; event_types[count].config_name != NULL; ++count) {
+ if (strcasecmp(when, event_types[count].config_name) != 0)
+ continue;
+ new->action.when = event_types[count].type;
+ break;
+ }
+ if (event_types[count].config_name == NULL) {
+ msg = "WHEN in";
+ goto process_config_line_err;
+ }
+
+ i = index_in_strings(options, what);
+
+ switch (i) {
+ case 4: /* "PERMISSIONS" */
+ new->action.what = AC_PERMISSIONS;
+ /* Get user and group */
+ ptr = strchr(p[0], '.');
+ if (ptr == NULL) {
+ msg = "UID.GID";
+ goto process_config_line_err; /*"missing '.' in UID.GID"*/
+ }
+
+ *ptr++ = '\0';
+ new->u.permissions.uid = get_uid_gid(UID, p[0]);
+ new->u.permissions.gid = get_uid_gid(GID, ptr);
+ /* Get mode */
+ new->u.permissions.mode = get_mode(p[1]);
+ break;
+ case 5: /* MODLOAD */
+ /*This action will pass "/dev/$devname"(i.e. "/dev/" prefixed to
+ the device name) to the module loading facility. In addition,
+ the /etc/modules.devfs configuration file is used.*/
+ if (ENABLE_DEVFSD_MODLOAD)
+ new->action.what = AC_MODLOAD;
+ break;
+ case 6: /* EXECUTE */
+ new->action.what = AC_EXECUTE;
+ num_args -= 3;
+
+ for (count = 0; count < num_args; ++count)
+ new->u.execute.argv[count] = xstrdup(p[count]);
+
+ new->u.execute.argv[num_args] = NULL;
+ break;
+ case 7: /* COPY */
+ new->action.what = AC_COPY;
+ num_args -= 3;
+ if (num_args != 2)
+ goto process_config_line_err; /* missing path and function in line */
+
+ new->u.copy.source = xstrdup(p[0]);
+ new->u.copy.destination = xstrdup(p[1]);
+ break;
+ case 8: /* IGNORE */
+ /* FALLTROUGH */
+ case 9: /* MKOLDCOMPAT */
+ /* FALLTROUGH */
+ case 10: /* MKNEWCOMPAT */
+ /* FALLTROUGH */
+ case 11:/* RMOLDCOMPAT */
+ /* FALLTROUGH */
+ case 12: /* RMNEWCOMPAT */
+ /* AC_IGNORE 6
+ AC_MKOLDCOMPAT 7
+ AC_MKNEWCOMPAT 8
+ AC_RMOLDCOMPAT 9
+ AC_RMNEWCOMPAT 10*/
+ new->action.what = i - 2;
+ break;
+ default:
+ msg = "WHAT in";
+ goto process_config_line_err;
+ /*esac*/
+ } /* switch (i) */
+
+ xregcomp(&new->preg, name, REG_EXTENDED);
+
+ *event_mask |= 1 << new->action.when;
+ new->next = NULL;
+ if (first_config == NULL)
+ first_config = new;
+ else
+ last_config->next = new;
+ last_config = new;
+ return;
+
+ process_config_line_err:
+ msg_logger_and_die(LOG_ERR, bb_msg_bad_config, msg , line);
+} /* End Function process_config_line */
+
+static int do_servicing(int fd, unsigned long event_mask)
+/* [SUMMARY] Service devfs changes until a signal is received.
+ <fd> The open control file.
+ <event_mask> The event mask.
+ [RETURNS] TRUE if SIGHUP was caught, else FALSE.
+*/
+{
+ ssize_t bytes;
+ struct devfsd_notify_struct info;
+
+ /* (void*) cast is only in order to match prototype */
+ xioctl(fd, DEVFSDIOC_SET_EVENT_MASK, (void*)event_mask);
+ while (!caught_signal) {
+ errno = 0;
+ bytes = read(fd,(char *) &info, sizeof info);
+ if (caught_signal)
+ break; /* Must test for this first */
+ if (errno == EINTR)
+ continue; /* Yes, the order is important */
+ if (bytes < 1)
+ break;
+ service_name(&info);
+ }
+ if (caught_signal) {
+ int c_sighup = caught_sighup;
+
+ caught_signal = FALSE;
+ caught_sighup = FALSE;
+ return c_sighup;
+ }
+ msg_logger_and_die(LOG_ERR, "read error on control file");
+} /* End Function do_servicing */
+
+static void service_name(const struct devfsd_notify_struct *info)
+/* [SUMMARY] Service a single devfs change.
+ <info> The devfs change.
+ [RETURNS] Nothing.
+*/
+{
+ unsigned int n;
+ regmatch_t mbuf[MAX_SUBEXPR];
+ struct config_entry_struct *entry;
+
+ if (ENABLE_DEBUG && info->overrun_count > 0)
+ msg_logger(LOG_ERR, "lost %u events", info->overrun_count);
+
+ /* Discard lookups on "/dev/log" and "/dev/initctl" */
+ if (info->type == DEVFSD_NOTIFY_LOOKUP
+ && ((info->devname[0] == 'l' && info->devname[1] == 'o'
+ && info->devname[2] == 'g' && !info->devname[3])
+ || (info->devname[0] == 'i' && info->devname[1] == 'n'
+ && info->devname[2] == 'i' && info->devname[3] == 't'
+ && info->devname[4] == 'c' && info->devname[5] == 't'
+ && info->devname[6] == 'l' && !info->devname[7]))
+ )
+ return;
+
+ for (entry = first_config; entry != NULL; entry = entry->next) {
+ /* First check if action matches the type, then check if name matches */
+ if (info->type != entry->action.when
+ || regexec(&entry->preg, info->devname, MAX_SUBEXPR, mbuf, 0) != 0)
+ continue;
+ for (n = 0;(n < MAX_SUBEXPR) && (mbuf[n].rm_so != -1); ++n)
+ /* VOID */;
+
+ switch (entry->action.what) {
+ case AC_PERMISSIONS:
+ action_permissions(info, entry);
+ break;
+ case AC_MODLOAD:
+ if (ENABLE_DEVFSD_MODLOAD)
+ action_modload(info, entry);
+ break;
+ case AC_EXECUTE:
+ action_execute(info, entry, mbuf, n);
+ break;
+ case AC_COPY:
+ action_copy(info, entry, mbuf, n);
+ break;
+ case AC_IGNORE:
+ return;
+ /*break;*/
+ case AC_MKOLDCOMPAT:
+ case AC_MKNEWCOMPAT:
+ case AC_RMOLDCOMPAT:
+ case AC_RMNEWCOMPAT:
+ action_compat(info, entry->action.what);
+ break;
+ default:
+ msg_logger_and_die(LOG_ERR, "Unknown action");
+ }
+ }
+} /* End Function service_name */
+
+static void action_permissions(const struct devfsd_notify_struct *info,
+ const struct config_entry_struct *entry)
+/* [SUMMARY] Update permissions for a device entry.
+ <info> The devfs change.
+ <entry> The config file entry.
+ [RETURNS] Nothing.
+*/
+{
+ struct stat statbuf;
+
+ if (stat(info->devname, &statbuf) != 0
+ || chmod(info->devname, (statbuf.st_mode & S_IFMT) | (entry->u.permissions.mode & ~S_IFMT)) != 0
+ || chown(info->devname, entry->u.permissions.uid, entry->u.permissions.gid) != 0
+ )
+ error_logger(LOG_ERR, "Can't chmod or chown: %s", info->devname);
+} /* End Function action_permissions */
+
+static void action_modload(const struct devfsd_notify_struct *info,
+ const struct config_entry_struct *entry UNUSED_PARAM)
+/* [SUMMARY] Load a module.
+ <info> The devfs change.
+ <entry> The config file entry.
+ [RETURNS] Nothing.
+*/
+{
+ char *argv[6];
+
+ argv[0] = (char*)MODPROBE;
+ argv[1] = (char*)MODPROBE_SWITCH_1; /* "-k" */
+ argv[2] = (char*)MODPROBE_SWITCH_2; /* "-C" */
+ argv[3] = (char*)CONFIG_MODULES_DEVFS;
+ argv[4] = concat_path_file("/dev", info->devname); /* device */
+ argv[5] = NULL;
+
+ wait4pid(xspawn(argv));
+ free(argv[4]);
+} /* End Function action_modload */
+
+static void action_execute(const struct devfsd_notify_struct *info,
+ const struct config_entry_struct *entry,
+ const regmatch_t *regexpr, unsigned int numexpr)
+/* [SUMMARY] Execute a programme.
+ <info> The devfs change.
+ <entry> The config file entry.
+ <regexpr> The number of subexpression(start, end) offsets within the
+ device name.
+ <numexpr> The number of elements within <<regexpr>>.
+ [RETURNS] Nothing.
+*/
+{
+ unsigned int count;
+ struct get_variable_info gv_info;
+ char *argv[MAX_ARGS + 1];
+ char largv[MAX_ARGS + 1][STRING_LENGTH];
+
+ gv_info.info = info;
+ gv_info.devname = info->devname;
+ snprintf(gv_info.devpath, sizeof(gv_info.devpath), "%s/%s", mount_point, info->devname);
+ for (count = 0; entry->u.execute.argv[count] != NULL; ++count) {
+ expand_expression(largv[count], STRING_LENGTH,
+ entry->u.execute.argv[count],
+ get_variable, &gv_info,
+ gv_info.devname, regexpr, numexpr);
+ argv[count] = largv[count];
+ }
+ argv[count] = NULL;
+ wait4pid(spawn(argv));
+} /* End Function action_execute */
+
+
+static void action_copy(const struct devfsd_notify_struct *info,
+ const struct config_entry_struct *entry,
+ const regmatch_t *regexpr, unsigned int numexpr)
+/* [SUMMARY] Copy permissions.
+ <info> The devfs change.
+ <entry> The config file entry.
+ <regexpr> This list of subexpression(start, end) offsets within the
+ device name.
+ <numexpr> The number of elements in <<regexpr>>.
+ [RETURNS] Nothing.
+*/
+{
+ mode_t new_mode;
+ struct get_variable_info gv_info;
+ struct stat source_stat, dest_stat;
+ char source[STRING_LENGTH], destination[STRING_LENGTH];
+ int ret = 0;
+
+ dest_stat.st_mode = 0;
+
+ if ((info->type == DEVFSD_NOTIFY_CHANGE) && S_ISLNK(info->mode))
+ return;
+ gv_info.info = info;
+ gv_info.devname = info->devname;
+
+ snprintf(gv_info.devpath, sizeof(gv_info.devpath), "%s/%s", mount_point, info->devname);
+ expand_expression(source, STRING_LENGTH, entry->u.copy.source,
+ get_variable, &gv_info, gv_info.devname,
+ regexpr, numexpr);
+
+ expand_expression(destination, STRING_LENGTH, entry->u.copy.destination,
+ get_variable, &gv_info, gv_info.devname,
+ regexpr, numexpr);
+
+ if (!make_dir_tree(destination) || lstat(source, &source_stat) != 0)
+ return;
+ lstat(destination, &dest_stat);
+ new_mode = source_stat.st_mode & ~S_ISVTX;
+ if (info->type == DEVFSD_NOTIFY_CREATE)
+ new_mode |= S_ISVTX;
+ else if ((info->type == DEVFSD_NOTIFY_CHANGE) &&(dest_stat.st_mode & S_ISVTX))
+ new_mode |= S_ISVTX;
+ ret = copy_inode(destination, &dest_stat, new_mode, source, &source_stat);
+ if (ENABLE_DEBUG && ret && (errno != EEXIST))
+ error_logger(LOG_ERR, "copy_inode: %s to %s", source, destination);
+} /* End Function action_copy */
+
+static void action_compat(const struct devfsd_notify_struct *info, unsigned int action)
+/* [SUMMARY] Process a compatibility request.
+ <info> The devfs change.
+ <action> The action to take.
+ [RETURNS] Nothing.
+*/
+{
+ int ret;
+ const char *compat_name = NULL;
+ const char *dest_name = info->devname;
+ const char *ptr;
+ char compat_buf[STRING_LENGTH], dest_buf[STRING_LENGTH];
+ int mode, host, bus, target, lun;
+ unsigned int i;
+ char rewind_;
+ /* 1 to 5 "scsi/" , 6 to 9 "ide/host" */
+ static const char *const fmt[] = {
+ NULL ,
+ "sg/c%db%dt%du%d", /* scsi/generic */
+ "sd/c%db%dt%du%d", /* scsi/disc */
+ "sr/c%db%dt%du%d", /* scsi/cd */
+ "sd/c%db%dt%du%dp%d", /* scsi/part */
+ "st/c%db%dt%du%dm%d%c", /* scsi/mt */
+ "ide/hd/c%db%dt%du%d", /* ide/host/disc */
+ "ide/cd/c%db%dt%du%d", /* ide/host/cd */
+ "ide/hd/c%db%dt%du%dp%d", /* ide/host/part */
+ "ide/mt/c%db%dt%du%d%s", /* ide/host/mt */
+ NULL
+ };
+
+ /* First construct compatibility name */
+ switch (action) {
+ case AC_MKOLDCOMPAT:
+ case AC_RMOLDCOMPAT:
+ compat_name = get_old_name(info->devname, info->namelen, compat_buf, info->major, info->minor);
+ break;
+ case AC_MKNEWCOMPAT:
+ case AC_RMNEWCOMPAT:
+ ptr = bb_basename(info->devname);
+ i = scan_dev_name(info->devname, info->namelen, ptr);
+
+ /* nothing found */
+ if (i == 0 || i > 9)
+ return;
+
+ sscanf(info->devname + ((i < 6) ? 5 : 4), "host%d/bus%d/target%d/lun%d/", &host, &bus, &target, &lun);
+ snprintf(dest_buf, sizeof(dest_buf), "../%s", info->devname + (( i > 5) ? 4 : 0));
+ dest_name = dest_buf;
+ compat_name = compat_buf;
+
+
+ /* 1 == scsi/generic 2 == scsi/disc 3 == scsi/cd 6 == ide/host/disc 7 == ide/host/cd */
+ if (i == 1 || i == 2 || i == 3 || i == 6 || i ==7)
+ sprintf(compat_buf, fmt[i], host, bus, target, lun);
+
+ /* 4 == scsi/part 8 == ide/host/part */
+ if (i == 4 || i == 8)
+ sprintf(compat_buf, fmt[i], host, bus, target, lun, atoi(ptr + 4));
+
+ /* 5 == scsi/mt */
+ if (i == 5) {
+ rewind_ = info->devname[info->namelen - 1];
+ if (rewind_ != 'n')
+ rewind_ = '\0';
+ mode=0;
+ if (ptr[2] == 'l' /*108*/ || ptr[2] == 'm'/*109*/)
+ mode = ptr[2] - 107; /* 1 or 2 */
+ if (ptr[2] == 'a')
+ mode = 3;
+ sprintf(compat_buf, fmt[i], host, bus, target, lun, mode, rewind_);
+ }
+
+ /* 9 == ide/host/mt */
+ if (i == 9)
+ snprintf(compat_buf, sizeof(compat_buf), fmt[i], host, bus, target, lun, ptr + 2);
+ /* esac */
+ } /* switch (action) */
+
+ if (compat_name == NULL)
+ return;
+
+ /* Now decide what to do with it */
+ switch (action) {
+ case AC_MKOLDCOMPAT:
+ case AC_MKNEWCOMPAT:
+ mksymlink(dest_name, compat_name);
+ break;
+ case AC_RMOLDCOMPAT:
+ case AC_RMNEWCOMPAT:
+ ret = unlink(compat_name);
+ if (ENABLE_DEBUG && ret)
+ error_logger(LOG_ERR, "unlink: %s", compat_name);
+ break;
+ /*esac*/
+ } /* switch (action) */
+} /* End Function action_compat */
+
+static void restore(char *spath, struct stat source_stat, int rootlen)
+{
+ char *dpath;
+ struct stat dest_stat;
+
+ dest_stat.st_mode = 0;
+ dpath = concat_path_file(mount_point, spath + rootlen);
+ lstat(dpath, &dest_stat);
+ free(dpath);
+ if (S_ISLNK(source_stat.st_mode) || (source_stat.st_mode & S_ISVTX))
+ copy_inode(dpath, &dest_stat,(source_stat.st_mode & ~S_ISVTX) , spath, &source_stat);
+
+ if (S_ISDIR(source_stat.st_mode))
+ dir_operation(RESTORE, spath, rootlen,NULL);
+}
+
+
+static int copy_inode(const char *destpath, const struct stat *dest_stat,
+ mode_t new_mode,
+ const char *sourcepath, const struct stat *source_stat)
+/* [SUMMARY] Copy an inode.
+ <destpath> The destination path. An existing inode may be deleted.
+ <dest_stat> The destination stat(2) information.
+ <new_mode> The desired new mode for the destination.
+ <sourcepath> The source path.
+ <source_stat> The source stat(2) information.
+ [RETURNS] TRUE on success, else FALSE.
+*/
+{
+ int source_len, dest_len;
+ char source_link[STRING_LENGTH], dest_link[STRING_LENGTH];
+ int fd, val;
+ struct sockaddr_un un_addr;
+ char symlink_val[STRING_LENGTH];
+
+ if ((source_stat->st_mode & S_IFMT) ==(dest_stat->st_mode & S_IFMT)) {
+ /* Same type */
+ if (S_ISLNK(source_stat->st_mode)) {
+ source_len = readlink(sourcepath, source_link, STRING_LENGTH - 1);
+ if ((source_len < 0)
+ || (dest_len = readlink(destpath, dest_link, STRING_LENGTH - 1)) < 0
+ )
+ return FALSE;
+ source_link[source_len] = '\0';
+ dest_link[dest_len] = '\0';
+ if ((source_len != dest_len) || (strcmp(source_link, dest_link) != 0)) {
+ unlink(destpath);
+ symlink(source_link, destpath);
+ }
+ return TRUE;
+ } /* Else not a symlink */
+ chmod(destpath, new_mode & ~S_IFMT);
+ chown(destpath, source_stat->st_uid, source_stat->st_gid);
+ return TRUE;
+ }
+ /* Different types: unlink and create */
+ unlink(destpath);
+ switch (source_stat->st_mode & S_IFMT) {
+ case S_IFSOCK:
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ break;
+ un_addr.sun_family = AF_UNIX;
+ snprintf(un_addr.sun_path, sizeof(un_addr.sun_path), "%s", destpath);
+ val = bind(fd,(struct sockaddr *) &un_addr,(int) sizeof un_addr);
+ close(fd);
+ if (val != 0 || chmod(destpath, new_mode & ~S_IFMT) != 0)
+ break;
+ goto do_chown;
+ case S_IFLNK:
+ val = readlink(sourcepath, symlink_val, STRING_LENGTH - 1);
+ if (val < 0)
+ break;
+ symlink_val[val] = '\0';
+ if (symlink(symlink_val, destpath) == 0)
+ return TRUE;
+ break;
+ case S_IFREG:
+ fd = open(destpath, O_RDONLY | O_CREAT, new_mode & ~S_IFMT);
+ if (fd < 0)
+ break;
+ close(fd);
+ if (chmod(destpath, new_mode & ~S_IFMT) != 0)
+ break;
+ goto do_chown;
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ if (mknod(destpath, new_mode, source_stat->st_rdev) != 0)
+ break;
+ goto do_chown;
+ case S_IFDIR:
+ if (mkdir(destpath, new_mode & ~S_IFMT) != 0)
+ break;
+do_chown:
+ if (chown(destpath, source_stat->st_uid, source_stat->st_gid) == 0)
+ return TRUE;
+ /*break;*/
+ }
+ return FALSE;
+} /* End Function copy_inode */
+
+static void free_config(void)
+/* [SUMMARY] Free the configuration information.
+ [RETURNS] Nothing.
+*/
+{
+ struct config_entry_struct *c_entry;
+ void *next;
+
+ for (c_entry = first_config; c_entry != NULL; c_entry = next) {
+ unsigned int count;
+
+ next = c_entry->next;
+ regfree(&c_entry->preg);
+ if (c_entry->action.what == AC_EXECUTE) {
+ for (count = 0; count < MAX_ARGS; ++count) {
+ if (c_entry->u.execute.argv[count] == NULL)
+ break;
+ free(c_entry->u.execute.argv[count]);
+ }
+ }
+ free(c_entry);
+ }
+ first_config = NULL;
+ last_config = NULL;
+} /* End Function free_config */
+
+static int get_uid_gid(int flag, const char *string)
+/* [SUMMARY] Convert a string to a UID or GID value.
+ <flag> "UID" or "GID".
+ <string> The string.
+ [RETURNS] The UID or GID value.
+*/
+{
+ struct passwd *pw_ent;
+ struct group *grp_ent;
+ static const char *msg;
+
+ if (ENABLE_DEVFSD_VERBOSE)
+ msg = "user";
+
+ if (isdigit(string[0]) ||((string[0] == '-') && isdigit(string[1])))
+ return atoi(string);
+
+ if (flag == UID && (pw_ent = getpwnam(string)) != NULL)
+ return pw_ent->pw_uid;
+
+ if (flag == GID && (grp_ent = getgrnam(string)) != NULL)
+ return grp_ent->gr_gid;
+ else if (ENABLE_DEVFSD_VERBOSE)
+ msg = "group";
+
+ if (ENABLE_DEVFSD_VERBOSE)
+ msg_logger(LOG_ERR, "unknown %s: %s, defaulting to %cid=0", msg, string, msg[0]);
+ return 0;
+}/* End Function get_uid_gid */
+
+static mode_t get_mode(const char *string)
+/* [SUMMARY] Convert a string to a mode value.
+ <string> The string.
+ [RETURNS] The mode value.
+*/
+{
+ mode_t mode;
+ int i;
+
+ if (isdigit(string[0]))
+ return strtoul(string, NULL, 8);
+ if (strlen(string) != 9)
+ msg_logger_and_die(LOG_ERR, "bad mode: %s", string);
+
+ mode = 0;
+ i = S_IRUSR;
+ while (i > 0) {
+ if (string[0] == 'r' || string[0] == 'w' || string[0] == 'x')
+ mode += i;
+ i = i / 2;
+ string++;
+ }
+ return mode;
+} /* End Function get_mode */
+
+static void signal_handler(int sig)
+{
+ caught_signal = TRUE;
+ if (sig == SIGHUP)
+ caught_sighup = TRUE;
+
+ info_logger(LOG_INFO, "Caught signal %d", sig);
+} /* End Function signal_handler */
+
+static const char *get_variable(const char *variable, void *info)
+{
+ static char sbuf[sizeof(int)*3 + 2]; /* sign and NUL */
+ static char *hostname;
+
+ struct get_variable_info *gv_info = info;
+ const char *field_names[] = {
+ "hostname", "mntpt", "devpath", "devname",
+ "uid", "gid", "mode", hostname, mount_point,
+ gv_info->devpath, gv_info->devname, NULL
+ };
+ int i;
+
+ if (!hostname)
+ hostname = safe_gethostname();
+ /* index_in_str_array returns i>=0 */
+ i = index_in_str_array(field_names, variable);
+
+ if (i > 6 || i < 0 || (i > 1 && gv_info == NULL))
+ return NULL;
+ if (i >= 0 && i <= 3)
+ return field_names[i + 7];
+
+ if (i == 4)
+ sprintf(sbuf, "%u", gv_info->info->uid);
+ else if (i == 5)
+ sprintf(sbuf, "%u", gv_info->info->gid);
+ else if (i == 6)
+ sprintf(sbuf, "%o", gv_info->info->mode);
+ return sbuf;
+} /* End Function get_variable */
+
+static void service(struct stat statbuf, char *path)
+{
+ struct devfsd_notify_struct info;
+
+ memset(&info, 0, sizeof info);
+ info.type = DEVFSD_NOTIFY_REGISTERED;
+ info.mode = statbuf.st_mode;
+ info.major = major(statbuf.st_rdev);
+ info.minor = minor(statbuf.st_rdev);
+ info.uid = statbuf.st_uid;
+ info.gid = statbuf.st_gid;
+ snprintf(info.devname, sizeof(info.devname), "%s", path + strlen(mount_point) + 1);
+ info.namelen = strlen(info.devname);
+ service_name(&info);
+ if (S_ISDIR(statbuf.st_mode))
+ dir_operation(SERVICE, path, 0, NULL);
+}
+
+static void dir_operation(int type, const char * dir_name, int var, unsigned long *event_mask)
+/* [SUMMARY] Scan a directory tree and generate register events on leaf nodes.
+ <flag> To choose which function to perform
+ <dp> The directory pointer. This is closed upon completion.
+ <dir_name> The name of the directory.
+ <rootlen> string length parameter.
+ [RETURNS] Nothing.
+*/
+{
+ struct stat statbuf;
+ DIR *dp;
+ struct dirent *de;
+ char *path;
+
+ dp = warn_opendir(dir_name);
+ if (dp == NULL)
+ return;
+
+ while ((de = readdir(dp)) != NULL) {
+
+ if (de->d_name && DOT_OR_DOTDOT(de->d_name))
+ continue;
+ path = concat_path_file(dir_name, de->d_name);
+ if (lstat(path, &statbuf) == 0) {
+ switch (type) {
+ case SERVICE:
+ service(statbuf, path);
+ break;
+ case RESTORE:
+ restore(path, statbuf, var);
+ break;
+ case READ_CONFIG:
+ read_config_file(path, var, event_mask);
+ break;
+ }
+ }
+ free(path);
+ }
+ closedir(dp);
+} /* End Function do_scan_and_service */
+
+static int mksymlink(const char *oldpath, const char *newpath)
+/* [SUMMARY] Create a symlink, creating intervening directories as required.
+ <oldpath> The string contained in the symlink.
+ <newpath> The name of the new symlink.
+ [RETURNS] 0 on success, else -1.
+*/
+{
+ if (!make_dir_tree(newpath))
+ return -1;
+
+ if (symlink(oldpath, newpath) != 0) {
+ if (errno != EEXIST)
+ return -1;
+ }
+ return 0;
+} /* End Function mksymlink */
+
+
+static int make_dir_tree(const char *path)
+/* [SUMMARY] Creating intervening directories for a path as required.
+ <path> The full pathname(including the leaf node).
+ [RETURNS] TRUE on success, else FALSE.
+*/
+{
+ if (bb_make_directory(dirname((char *)path), -1, FILEUTILS_RECUR) == -1)
+ return FALSE;
+ return TRUE;
+} /* End Function make_dir_tree */
+
+static int expand_expression(char *output, unsigned int outsize,
+ const char *input,
+ const char *(*get_variable_func)(const char *variable, void *info),
+ void *info,
+ const char *devname,
+ const regmatch_t *ex, unsigned int numexp)
+/* [SUMMARY] Expand environment variables and regular subexpressions in string.
+ <output> The output expanded expression is written here.
+ <length> The size of the output buffer.
+ <input> The input expression. This may equal <<output>>.
+ <get_variable> A function which will be used to get variable values. If
+ this returns NULL, the environment is searched instead. If this is NULL,
+ only the environment is searched.
+ <info> An arbitrary pointer passed to <<get_variable>>.
+ <devname> Device name; specifically, this is the string that contains all
+ of the regular subexpressions.
+ <ex> Array of start / end offsets into info->devname for each subexpression
+ <numexp> Number of regular subexpressions found in <<devname>>.
+ [RETURNS] TRUE on success, else FALSE.
+*/
+{
+ char temp[STRING_LENGTH];
+
+ if (!st_expr_expand(temp, STRING_LENGTH, input, get_variable_func, info))
+ return FALSE;
+ expand_regexp(output, outsize, temp, devname, ex, numexp);
+ return TRUE;
+} /* End Function expand_expression */
+
+static void expand_regexp(char *output, size_t outsize, const char *input,
+ const char *devname,
+ const regmatch_t *ex, unsigned int numex)
+/* [SUMMARY] Expand all occurrences of the regular subexpressions \0 to \9.
+ <output> The output expanded expression is written here.
+ <outsize> The size of the output buffer.
+ <input> The input expression. This may NOT equal <<output>>, because
+ supporting that would require yet another string-copy. However, it's not
+ hard to write a simple wrapper function to add this functionality for those
+ few cases that need it.
+ <devname> Device name; specifically, this is the string that contains all
+ of the regular subexpressions.
+ <ex> An array of start and end offsets into <<devname>>, one for each
+ subexpression
+ <numex> Number of subexpressions in the offset-array <<ex>>.
+ [RETURNS] Nothing.
+*/
+{
+ const char last_exp = '0' - 1 + numex;
+ int c = -1;
+
+ /* Guarantee NULL termination by writing an explicit '\0' character into
+ the very last byte */
+ if (outsize)
+ output[--outsize] = '\0';
+ /* Copy the input string into the output buffer, replacing '\\' with '\'
+ and '\0' .. '\9' with subexpressions 0 .. 9, if they exist. Other \x
+ codes are deleted */
+ while ((c != '\0') && (outsize != 0)) {
+ c = *input;
+ ++input;
+ if (c == '\\') {
+ c = *input;
+ ++input;
+ if (c != '\\') {
+ if ((c >= '0') && (c <= last_exp)) {
+ const regmatch_t *subexp = ex + (c - '0');
+ unsigned int sublen = subexp->rm_eo - subexp->rm_so;
+
+ /* Range checking */
+ if (sublen > outsize)
+ sublen = outsize;
+ strncpy(output, devname + subexp->rm_so, sublen);
+ output += sublen;
+ outsize -= sublen;
+ }
+ continue;
+ }
+ }
+ *output = c;
+ ++output;
+ --outsize;
+ } /* while */
+} /* End Function expand_regexp */
+
+
+/* from compat_name.c */
+
+struct translate_struct
+{
+ const char *match; /* The string to match to(up to length) */
+ const char *format; /* Format of output, "%s" takes data past match string,
+ NULL is effectively "%s"(just more efficient) */
+};
+
+static struct translate_struct translate_table[] =
+{
+ {"sound/", NULL},
+ {"printers/", "lp%s"},
+ {"v4l/", NULL},
+ {"parports/", "parport%s"},
+ {"fb/", "fb%s"},
+ {"netlink/", NULL},
+ {"loop/", "loop%s"},
+ {"floppy/", "fd%s"},
+ {"rd/", "ram%s"},
+ {"md/", "md%s"}, /* Meta-devices */
+ {"vc/", "tty%s"},
+ {"misc/", NULL},
+ {"isdn/", NULL},
+ {"pg/", "pg%s"}, /* Parallel port generic ATAPI interface*/
+ {"i2c/", "i2c-%s"},
+ {"staliomem/", "staliomem%s"}, /* Stallion serial driver control */
+ {"tts/E", "ttyE%s"}, /* Stallion serial driver */
+ {"cua/E", "cue%s"}, /* Stallion serial driver callout */
+ {"tts/R", "ttyR%s"}, /* Rocketport serial driver */
+ {"cua/R", "cur%s"}, /* Rocketport serial driver callout */
+ {"ip2/", "ip2%s"}, /* Computone serial driver control */
+ {"tts/F", "ttyF%s"}, /* Computone serial driver */
+ {"cua/F", "cuf%s"}, /* Computone serial driver callout */
+ {"tts/C", "ttyC%s"}, /* Cyclades serial driver */
+ {"cua/C", "cub%s"}, /* Cyclades serial driver callout */
+ {"tts/", "ttyS%s"}, /* Generic serial: must be after others */
+ {"cua/", "cua%s"}, /* Generic serial: must be after others */
+ {"input/js", "js%s"}, /* Joystick driver */
+ {NULL, NULL}
+};
+
+const char *get_old_name(const char *devname, unsigned int namelen,
+ char *buffer, unsigned int major, unsigned int minor)
+/* [SUMMARY] Translate a kernel-supplied name into an old name.
+ <devname> The device name provided by the kernel.
+ <namelen> The length of the name.
+ <buffer> A buffer that may be used. This should be at least 128 bytes long.
+ <major> The major number for the device.
+ <minor> The minor number for the device.
+ [RETURNS] A pointer to the old name if known, else NULL.
+*/
+{
+ const char *compat_name = NULL;
+ const char *ptr;
+ struct translate_struct *trans;
+ unsigned int i;
+ char mode;
+ int indexx;
+ const char *pty1;
+ const char *pty2;
+ size_t len;
+ /* 1 to 5 "scsi/" , 6 to 9 "ide/host", 10 sbp/, 11 vcc/, 12 pty/ */
+ static const char *const fmt[] = {
+ NULL ,
+ "sg%u", /* scsi/generic */
+ NULL, /* scsi/disc */
+ "sr%u", /* scsi/cd */
+ NULL, /* scsi/part */
+ "nst%u%c", /* scsi/mt */
+ "hd%c" , /* ide/host/disc */
+ "hd%c" , /* ide/host/cd */
+ "hd%c%s", /* ide/host/part */
+ "%sht%d", /* ide/host/mt */
+ "sbpcd%u", /* sbp/ */
+ "vcs%s", /* vcc/ */
+ "%cty%c%c", /* pty/ */
+ NULL
+ };
+
+ for (trans = translate_table; trans->match != NULL; ++trans) {
+ len = strlen(trans->match);
+
+ if (strncmp(devname, trans->match, len) == 0) {
+ if (trans->format == NULL)
+ return devname + len;
+ sprintf(buffer, trans->format, devname + len);
+ return buffer;
+ }
+ }
+
+ ptr = bb_basename(devname);
+ i = scan_dev_name(devname, namelen, ptr);
+
+ if (i > 0 && i < 13)
+ compat_name = buffer;
+ else
+ return NULL;
+
+ /* 1 == scsi/generic, 3 == scsi/cd, 10 == sbp/ */
+ if (i == 1 || i == 3 || i == 10)
+ sprintf(buffer, fmt[i], minor);
+
+ /* 2 ==scsi/disc, 4 == scsi/part */
+ if (i == 2 || i == 4)
+ compat_name = write_old_sd_name(buffer, major, minor,((i == 2) ? "" : (ptr + 4)));
+
+ /* 5 == scsi/mt */
+ if (i == 5) {
+ mode = ptr[2];
+ if (mode == 'n')
+ mode = '\0';
+ sprintf(buffer, fmt[i], minor & 0x1f, mode);
+ if (devname[namelen - 1] != 'n')
+ ++compat_name;
+ }
+ /* 6 == ide/host/disc, 7 == ide/host/cd, 8 == ide/host/part */
+ if (i == 6 || i == 7 || i == 8)
+ /* last arg should be ignored for i == 6 or i== 7 */
+ sprintf(buffer, fmt[i] , get_old_ide_name(major, minor), ptr + 4);
+
+ /* 9 == ide/host/mt */
+ if (i == 9)
+ sprintf(buffer, fmt[i], ptr + 2, minor & 0x7f);
+
+ /* 11 == vcc/ */
+ if (i == 11) {
+ sprintf(buffer, fmt[i], devname + 4);
+ if (buffer[3] == '0')
+ buffer[3] = '\0';
+ }
+ /* 12 == pty/ */
+ if (i == 12) {
+ pty1 = "pqrstuvwxyzabcde";
+ pty2 = "0123456789abcdef";
+ indexx = atoi(devname + 5);
+ sprintf(buffer, fmt[i], (devname[4] == 'm') ? 'p' : 't', pty1[indexx >> 4], pty2[indexx & 0x0f]);
+ }
+ return compat_name;
+} /* End Function get_old_name */
+
+static char get_old_ide_name(unsigned int major, unsigned int minor)
+/* [SUMMARY] Get the old IDE name for a device.
+ <major> The major number for the device.
+ <minor> The minor number for the device.
+ [RETURNS] The drive letter.
+*/
+{
+ char letter = 'y'; /* 121 */
+ char c = 'a'; /* 97 */
+ int i = IDE0_MAJOR;
+
+ /* I hope it works like the previous code as it saves a few bytes. Tito ;P */
+ do {
+ if (i == IDE0_MAJOR || i == IDE1_MAJOR || i == IDE2_MAJOR
+ || i == IDE3_MAJOR || i == IDE4_MAJOR || i == IDE5_MAJOR
+ || i == IDE6_MAJOR || i == IDE7_MAJOR || i == IDE8_MAJOR
+ || i == IDE9_MAJOR
+ ) {
+ if ((unsigned int)i == major) {
+ letter = c;
+ break;
+ }
+ c += 2;
+ }
+ i++;
+ } while (i <= IDE9_MAJOR);
+
+ if (minor > 63)
+ ++letter;
+ return letter;
+} /* End Function get_old_ide_name */
+
+static char *write_old_sd_name(char *buffer,
+ unsigned int major, unsigned int minor,
+ const char *part)
+/* [SUMMARY] Write the old SCSI disc name to a buffer.
+ <buffer> The buffer to write to.
+ <major> The major number for the device.
+ <minor> The minor number for the device.
+ <part> The partition string. Must be "" for a whole-disc entry.
+ [RETURNS] A pointer to the buffer on success, else NULL.
+*/
+{
+ unsigned int disc_index;
+
+ if (major == 8) {
+ sprintf(buffer, "sd%c%s", 'a' + (minor >> 4), part);
+ return buffer;
+ }
+ if ((major > 64) && (major < 72)) {
+ disc_index = ((major - 64) << 4) +(minor >> 4);
+ if (disc_index < 26)
+ sprintf(buffer, "sd%c%s", 'a' + disc_index, part);
+ else
+ sprintf(buffer, "sd%c%c%s", 'a' +(disc_index / 26) - 1, 'a' + disc_index % 26, part);
+ return buffer;
+ }
+ return NULL;
+} /* End Function write_old_sd_name */
+
+
+/* expression.c */
+
+/*EXPERIMENTAL_FUNCTION*/
+
+int st_expr_expand(char *output, unsigned int length, const char *input,
+ const char *(*get_variable_func)(const char *variable,
+ void *info),
+ void *info)
+/* [SUMMARY] Expand an expression using Borne Shell-like unquoted rules.
+ <output> The output expanded expression is written here.
+ <length> The size of the output buffer.
+ <input> The input expression. This may equal <<output>>.
+ <get_variable> A function which will be used to get variable values. If
+ this returns NULL, the environment is searched instead. If this is NULL,
+ only the environment is searched.
+ <info> An arbitrary pointer passed to <<get_variable>>.
+ [RETURNS] TRUE on success, else FALSE.
+*/
+{
+ char ch;
+ unsigned int len;
+ unsigned int out_pos = 0;
+ const char *env;
+ const char *ptr;
+ struct passwd *pwent;
+ char buffer[BUFFER_SIZE], tmp[STRING_LENGTH];
+
+ if (length > BUFFER_SIZE)
+ length = BUFFER_SIZE;
+ for (; TRUE; ++input) {
+ switch (ch = *input) {
+ case '$':
+ /* Variable expansion */
+ input = expand_variable(buffer, length, &out_pos, ++input, get_variable_func, info);
+ if (input == NULL)
+ return FALSE;
+ break;
+ case '~':
+ /* Home directory expansion */
+ ch = input[1];
+ if (isspace(ch) ||(ch == '/') ||(ch == '\0')) {
+ /* User's own home directory: leave separator for next time */
+ env = getenv("HOME");
+ if (env == NULL) {
+ info_logger(LOG_INFO, bb_msg_variable_not_found, "HOME");
+ return FALSE;
+ }
+ len = strlen(env);
+ if (len + out_pos >= length)
+ goto st_expr_expand_out;
+ memcpy(buffer + out_pos, env, len + 1);
+ out_pos += len;
+ continue;
+ }
+ /* Someone else's home directory */
+ for (ptr = ++input; !isspace(ch) && (ch != '/') && (ch != '\0'); ch = *++ptr)
+ /* VOID */;
+ len = ptr - input;
+ if (len >= sizeof tmp)
+ goto st_expr_expand_out;
+ safe_memcpy(tmp, input, len);
+ input = ptr - 1;
+ pwent = getpwnam(tmp);
+ if (pwent == NULL) {
+ info_logger(LOG_INFO, "no pwent for: %s", tmp);
+ return FALSE;
+ }
+ len = strlen(pwent->pw_dir);
+ if (len + out_pos >= length)
+ goto st_expr_expand_out;
+ memcpy(buffer + out_pos, pwent->pw_dir, len + 1);
+ out_pos += len;
+ break;
+ case '\0':
+ /* Falltrough */
+ default:
+ if (out_pos >= length)
+ goto st_expr_expand_out;
+ buffer[out_pos++] = ch;
+ if (ch == '\0') {
+ memcpy(output, buffer, out_pos);
+ return TRUE;
+ }
+ break;
+ /* esac */
+ }
+ }
+ return FALSE;
+st_expr_expand_out:
+ info_logger(LOG_INFO, bb_msg_small_buffer);
+ return FALSE;
+} /* End Function st_expr_expand */
+
+
+/* Private functions follow */
+
+static const char *expand_variable(char *buffer, unsigned int length,
+ unsigned int *out_pos, const char *input,
+ const char *(*func)(const char *variable,
+ void *info),
+ void *info)
+/* [SUMMARY] Expand a variable.
+ <buffer> The buffer to write to.
+ <length> The length of the output buffer.
+ <out_pos> The current output position. This is updated.
+ <input> A pointer to the input character pointer.
+ <func> A function which will be used to get variable values. If this
+ returns NULL, the environment is searched instead. If this is NULL, only
+ the environment is searched.
+ <info> An arbitrary pointer passed to <<func>>.
+ <errfp> Diagnostic messages are written here.
+ [RETURNS] A pointer to the end of this subexpression on success, else NULL.
+*/
+{
+ char ch;
+ int len;
+ unsigned int open_braces;
+ const char *env, *ptr;
+ char tmp[STRING_LENGTH];
+
+ ch = input[0];
+ if (ch == '$') {
+ /* Special case for "$$": PID */
+ sprintf(tmp, "%d",(int) getpid());
+ len = strlen(tmp);
+ if (len + *out_pos >= length)
+ goto expand_variable_out;
+
+ memcpy(buffer + *out_pos, tmp, len + 1);
+ out_pos += len;
+ return input;
+ }
+ /* Ordinary variable expansion, possibly in braces */
+ if (ch != '{') {
+ /* Simple variable expansion */
+ for (ptr = input; isalnum(ch) || (ch == '_') || (ch == ':'); ch = *++ptr)
+ /* VOID */;
+ len = ptr - input;
+ if ((size_t)len >= sizeof tmp)
+ goto expand_variable_out;
+
+ safe_memcpy(tmp, input, len);
+ input = ptr - 1;
+ env = get_variable_v2(tmp, func, info);
+ if (env == NULL) {
+ info_logger(LOG_INFO, bb_msg_variable_not_found, tmp);
+ return NULL;
+ }
+ len = strlen(env);
+ if (len + *out_pos >= length)
+ goto expand_variable_out;
+
+ memcpy(buffer + *out_pos, env, len + 1);
+ *out_pos += len;
+ return input;
+ }
+ /* Variable in braces: check for ':' tricks */
+ ch = *++input;
+ for (ptr = input; isalnum(ch) || (ch == '_'); ch = *++ptr)
+ /* VOID */;
+ if (ch == '}') {
+ /* Must be simple variable expansion with "${var}" */
+ len = ptr - input;
+ if ((size_t)len >= sizeof tmp)
+ goto expand_variable_out;
+
+ safe_memcpy(tmp, input, len);
+ ptr = expand_variable(buffer, length, out_pos, tmp, func, info);
+ if (ptr == NULL)
+ return NULL;
+ return input + len;
+ }
+ if (ch != ':' || ptr[1] != '-') {
+ info_logger(LOG_INFO, "illegal char in var name");
+ return NULL;
+ }
+ /* It's that handy "${var:-word}" expression. Check if var is defined */
+ len = ptr - input;
+ if ((size_t)len >= sizeof tmp)
+ goto expand_variable_out;
+
+ safe_memcpy(tmp, input, len);
+ /* Move input pointer to ':' */
+ input = ptr;
+ /* First skip to closing brace, taking note of nested expressions */
+ ptr += 2;
+ ch = ptr[0];
+ for (open_braces = 1; open_braces > 0; ch = *++ptr) {
+ switch (ch) {
+ case '{':
+ ++open_braces;
+ break;
+ case '}':
+ --open_braces;
+ break;
+ case '\0':
+ info_logger(LOG_INFO, "\"}\" not found in: %s", input);
+ return NULL;
+ default:
+ break;
+ }
+ }
+ --ptr;
+ /* At this point ptr should point to closing brace of "${var:-word}" */
+ env = get_variable_v2(tmp, func, info);
+ if (env != NULL) {
+ /* Found environment variable, so skip the input to the closing brace
+ and return the variable */
+ input = ptr;
+ len = strlen(env);
+ if (len + *out_pos >= length)
+ goto expand_variable_out;
+
+ memcpy(buffer + *out_pos, env, len + 1);
+ *out_pos += len;
+ return input;
+ }
+ /* Environment variable was not found, so process word. Advance input
+ pointer to start of word in "${var:-word}" */
+ input += 2;
+ len = ptr - input;
+ if ((size_t)len >= sizeof tmp)
+ goto expand_variable_out;
+
+ safe_memcpy(tmp, input, len);
+ input = ptr;
+ if (!st_expr_expand(tmp, STRING_LENGTH, tmp, func, info))
+ return NULL;
+ len = strlen(tmp);
+ if (len + *out_pos >= length)
+ goto expand_variable_out;
+
+ memcpy(buffer + *out_pos, tmp, len + 1);
+ *out_pos += len;
+ return input;
+expand_variable_out:
+ info_logger(LOG_INFO, bb_msg_small_buffer);
+ return NULL;
+} /* End Function expand_variable */
+
+
+static const char *get_variable_v2(const char *variable,
+ const char *(*func)(const char *variable, void *info),
+ void *info)
+/* [SUMMARY] Get a variable from the environment or .
+ <variable> The variable name.
+ <func> A function which will be used to get the variable. If this returns
+ NULL, the environment is searched instead. If this is NULL, only the
+ environment is searched.
+ [RETURNS] The value of the variable on success, else NULL.
+*/
+{
+ const char *value;
+
+ if (func != NULL) {
+ value = (*func)(variable, info);
+ if (value != NULL)
+ return value;
+ }
+ return getenv(variable);
+} /* End Function get_variable */
+
+/* END OF CODE */
diff --git a/miscutils/devmem.c b/miscutils/devmem.c
new file mode 100644
index 0000000..e13dedc
--- /dev/null
+++ b/miscutils/devmem.c
@@ -0,0 +1,128 @@
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ * Copyright (C) 2000, Jan-Derk Bakker (J.D.Bakker@its.tudelft.nl)
+ * Copyright (C) 2008, BusyBox Team. -solar 4/26/08
+ */
+
+#include "libbb.h"
+
+int devmem_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int devmem_main(int argc UNUSED_PARAM, char **argv)
+{
+ void *map_base, *virt_addr;
+ uint64_t read_result;
+ uint64_t writeval = writeval; /* for compiler */
+ off_t target;
+ unsigned page_size = getpagesize();
+ int fd;
+ int width = 8 * sizeof(int);
+
+ /* devmem ADDRESS [WIDTH [VALUE]] */
+// TODO: options?
+// -r: read and output only the value in hex, with 0x prefix
+// -w: write only, no reads before or after, and no output
+// or make this behavior default?
+// Let's try this and see how users react.
+
+ /* ADDRESS */
+ if (!argv[1])
+ bb_show_usage();
+ errno = 0;
+ target = bb_strtoull(argv[1], NULL, 0); /* allows hex, oct etc */
+
+ /* WIDTH */
+ if (argv[2]) {
+ if (isdigit(argv[2][0]) || argv[2][1])
+ width = xatou(argv[2]);
+ else {
+ static const char bhwl[] ALIGN1 = "bhwl";
+ static const uint8_t sizes[] ALIGN1 = {
+ 8 * sizeof(char),
+ 8 * sizeof(short),
+ 8 * sizeof(int),
+ 8 * sizeof(long),
+ 0 /* bad */
+ };
+ width = strchrnul(bhwl, (argv[2][0] | 0x20)) - bhwl;
+ width = sizes[width];
+ }
+ /* VALUE */
+ if (argv[3])
+ writeval = bb_strtoull(argv[3], NULL, 0);
+ } else { /* argv[2] == NULL */
+ /* make argv[3] to be a valid thing to use */
+ argv--;
+ }
+ if (errno)
+ bb_show_usage(); /* bb_strtouXX failed */
+
+ fd = xopen("/dev/mem", argv[3] ? (O_RDWR | O_SYNC) : (O_RDONLY | O_SYNC));
+ map_base = mmap(NULL,
+ page_size * 2 /* in case value spans page */,
+ argv[3] ? (PROT_READ | PROT_WRITE) : PROT_READ,
+ MAP_SHARED,
+ fd,
+ target & ~(off_t)(page_size - 1));
+ if (map_base == MAP_FAILED)
+ bb_perror_msg_and_die("mmap");
+
+// printf("Memory mapped at address %p.\n", map_base);
+
+ virt_addr = (char*)map_base + (target & (page_size - 1));
+
+ if (!argv[3]) {
+ switch (width) {
+ case 8:
+ read_result = *(volatile uint8_t*)virt_addr;
+ break;
+ case 16:
+ read_result = *(volatile uint16_t*)virt_addr;
+ break;
+ case 32:
+ read_result = *(volatile uint32_t*)virt_addr;
+ break;
+ case 64:
+ read_result = *(volatile uint64_t*)virt_addr;
+ break;
+ default:
+ bb_error_msg_and_die("bad width");
+ }
+// printf("Value at address 0x%"OFF_FMT"X (%p): 0x%llX\n",
+// target, virt_addr,
+// (unsigned long long)read_result);
+ /* Zero-padded output shows the width of access just done */
+ printf("0x%0*llX\n", (width >> 2), (unsigned long long)read_result);
+ } else {
+ switch (width) {
+ case 8:
+ *(volatile uint8_t*)virt_addr = writeval;
+// read_result = *(volatile uint8_t*)virt_addr;
+ break;
+ case 16:
+ *(volatile uint16_t*)virt_addr = writeval;
+// read_result = *(volatile uint16_t*)virt_addr;
+ break;
+ case 32:
+ *(volatile uint32_t*)virt_addr = writeval;
+// read_result = *(volatile uint32_t*)virt_addr;
+ break;
+ case 64:
+ *(volatile uint64_t*)virt_addr = writeval;
+// read_result = *(volatile uint64_t*)virt_addr;
+ break;
+ default:
+ bb_error_msg_and_die("bad width");
+ }
+// printf("Written 0x%llX; readback 0x%llX\n",
+// (unsigned long long)writeval,
+// (unsigned long long)read_result);
+ }
+
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ if (munmap(map_base, page_size * 2) == -1)
+ bb_perror_msg_and_die("munmap");
+ close(fd);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/eject.c b/miscutils/eject.c
new file mode 100644
index 0000000..ff3976e
--- /dev/null
+++ b/miscutils/eject.c
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * eject implementation for busybox
+ *
+ * Copyright (C) 2004 Peter Willis <psyphreak@phreaker.net>
+ * Copyright (C) 2005 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/*
+ * This is a simple hack of eject based on something Erik posted in #uclibc.
+ * Most of the dirty work blatantly ripped off from cat.c =)
+ */
+
+#include "libbb.h"
+
+/* various defines swiped from linux/cdrom.h */
+#define CDROMCLOSETRAY 0x5319 /* pendant of CDROMEJECT */
+#define CDROMEJECT 0x5309 /* Ejects the cdrom media */
+#define CDROM_DRIVE_STATUS 0x5326 /* Get tray position, etc. */
+/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */
+#define CDS_TRAY_OPEN 2
+
+#define dev_fd 3
+
+/* Code taken from the original eject (http://eject.sourceforge.net/),
+ * refactored it a bit for busybox (ne-bb@nicoerfurth.de) */
+
+#include <scsi/sg.h>
+#include <scsi/scsi.h>
+
+static void eject_scsi(const char *dev)
+{
+ static const char sg_commands[3][6] = {
+ { ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0 },
+ { START_STOP, 0, 0, 0, 1, 0 },
+ { START_STOP, 0, 0, 0, 2, 0 }
+ };
+
+ unsigned i;
+ unsigned char sense_buffer[32];
+ unsigned char inqBuff[2];
+ sg_io_hdr_t io_hdr;
+
+ if ((ioctl(dev_fd, SG_GET_VERSION_NUM, &i) < 0) || (i < 30000))
+ bb_error_msg_and_die("not a sg device or old sg driver");
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = 6;
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ /* io_hdr.dxfer_len = 0; */
+ io_hdr.dxferp = inqBuff;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 2000;
+
+ for (i = 0; i < 3; i++) {
+ io_hdr.cmdp = (void *)sg_commands[i];
+ ioctl_or_perror_and_die(dev_fd, SG_IO, (void *)&io_hdr, "%s", dev);
+ }
+
+ /* force kernel to reread partition table when new disc is inserted */
+ ioctl(dev_fd, BLKRRPART);
+}
+
+#define FLAG_CLOSE 1
+#define FLAG_SMART 2
+#define FLAG_SCSI 4
+
+static void eject_cdrom(unsigned flags, const char *dev)
+{
+ int cmd = CDROMEJECT;
+
+ if (flags & FLAG_CLOSE
+ || (flags & FLAG_SMART && ioctl(dev_fd, CDROM_DRIVE_STATUS) == CDS_TRAY_OPEN)
+ ) {
+ cmd = CDROMCLOSETRAY;
+ }
+
+ ioctl_or_perror_and_die(dev_fd, cmd, NULL, "%s", dev);
+}
+
+int eject_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int eject_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned flags;
+ const char *device;
+
+ opt_complementary = "?1:t--T:T--t";
+ flags = getopt32(argv, "tT" USE_FEATURE_EJECT_SCSI("s"));
+ device = argv[optind] ? argv[optind] : "/dev/cdrom";
+
+ /* We used to do "umount <device>" here, but it was buggy
+ if something was mounted OVER cdrom and
+ if cdrom is mounted many times.
+
+ This works equally well (or better):
+ #!/bin/sh
+ umount /dev/cdrom
+ eject /dev/cdrom
+ */
+
+ xmove_fd(xopen(device, O_RDONLY|O_NONBLOCK), dev_fd);
+
+ if (ENABLE_FEATURE_EJECT_SCSI && (flags & FLAG_SCSI))
+ eject_scsi(device);
+ else
+ eject_cdrom(flags, device);
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(dev_fd);
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/fbsplash.c b/miscutils/fbsplash.c
new file mode 100644
index 0000000..f8289c3
--- /dev/null
+++ b/miscutils/fbsplash.c
@@ -0,0 +1,393 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 Michele Sanges <michele.sanges@otomelara.it>,
+ * <michele.sanges@gmail.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Usage:
+ * - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
+ * - put somewhere fbsplash.cfg file and an image in .ppm format.
+ * - run applet: $ setsid fbsplash [params] &
+ * -c: hide cursor
+ * -d /dev/fbN: framebuffer device (if not /dev/fb0)
+ * -s path_to_image_file (can be "-" for stdin)
+ * -i path_to_cfg_file
+ * -f path_to_fifo (can be "-" for stdin)
+ * - if you want to run it only in presence of a kernel parameter
+ * (for example fbsplash=on), use:
+ * grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params]
+ * - commands for fifo:
+ * "NN" (ASCII decimal number) - percentage to show on progress bar.
+ * "exit" (or just close fifo) - well you guessed it.
+ */
+
+#include "libbb.h"
+#include <linux/fb.h>
+
+/* If you want logging messages on /tmp/fbsplash.log... */
+#define DEBUG 0
+
+#define BYTES_PER_PIXEL 2
+
+typedef unsigned short DATA;
+
+struct globals {
+#if DEBUG
+ bool bdebug_messages; // enable/disable logging
+ FILE *logfile_fd; // log file
+#endif
+ unsigned char *addr; // pointer to framebuffer memory
+ unsigned ns[7]; // n-parameters
+ const char *image_filename;
+ struct fb_var_screeninfo scr_var;
+ struct fb_fix_screeninfo scr_fix;
+};
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+#define nbar_width ns[0] // progress bar width
+#define nbar_height ns[1] // progress bar height
+#define nbar_posx ns[2] // progress bar horizontal position
+#define nbar_posy ns[3] // progress bar vertical position
+#define nbar_colr ns[4] // progress bar color red component
+#define nbar_colg ns[5] // progress bar color green component
+#define nbar_colb ns[6] // progress bar color blue component
+
+#if DEBUG
+#define DEBUG_MESSAGE(strMessage, args...) \
+ if (G.bdebug_messages) { \
+ fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
+ __FILE__, __FUNCTION__, strMessage); \
+ }
+#else
+#define DEBUG_MESSAGE(...) ((void)0)
+#endif
+
+
+/**
+ * Open and initialize the framebuffer device
+ * \param *strfb_device pointer to framebuffer device
+ */
+static void fb_open(const char *strfb_device)
+{
+ int fbfd = xopen(strfb_device, O_RDWR);
+
+ // framebuffer properties
+ xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
+ xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
+
+ if (G.scr_var.bits_per_pixel != 16)
+ bb_error_msg_and_die("only 16 bpp is supported");
+
+ // map the device in memory
+ G.addr = mmap(NULL,
+ G.scr_var.xres * G.scr_var.yres
+ * BYTES_PER_PIXEL /*(G.scr_var.bits_per_pixel / 8)*/ ,
+ PROT_WRITE, MAP_SHARED, fbfd, 0);
+ if (G.addr == MAP_FAILED)
+ bb_perror_msg_and_die("can't mmap %s", strfb_device);
+ close(fbfd);
+}
+
+
+/**
+ * Draw hollow rectangle on framebuffer
+ */
+static void fb_drawrectangle(void)
+{
+ int cnt;
+ DATA thispix;
+ DATA *ptr1, *ptr2;
+ unsigned char nred = G.nbar_colr/2;
+ unsigned char ngreen = G.nbar_colg/2;
+ unsigned char nblue = G.nbar_colb/2;
+
+ nred >>= 3; // 5-bit red
+ ngreen >>= 2; // 6-bit green
+ nblue >>= 3; // 5-bit blue
+ thispix = nblue + (ngreen << 5) + (nred << (5+6));
+
+ // horizontal lines
+ ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+ ptr2 = (DATA*)(G.addr + ((G.nbar_posy + G.nbar_height - 1) * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+ cnt = G.nbar_width - 1;
+ do {
+ *ptr1++ = thispix;
+ *ptr2++ = thispix;
+ } while (--cnt >= 0);
+
+ // vertical lines
+ ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+ ptr2 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx + G.nbar_width - 1) * BYTES_PER_PIXEL);
+ cnt = G.nbar_posy + G.nbar_height - 1 - G.nbar_posy;
+ do {
+ *ptr1 = thispix; ptr1 += G.scr_var.xres;
+ *ptr2 = thispix; ptr2 += G.scr_var.xres;
+ } while (--cnt >= 0);
+}
+
+
+/**
+ * Draw filled rectangle on framebuffer
+ * \param nx1pos,ny1pos upper left position
+ * \param nx2pos,ny2pos down right position
+ * \param nred,ngreen,nblue rgb color
+ */
+static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
+ unsigned char nred, unsigned char ngreen, unsigned char nblue)
+{
+ int cnt1, cnt2, nypos;
+ DATA thispix;
+ DATA *ptr;
+
+ nred >>= 3; // 5-bit red
+ ngreen >>= 2; // 6-bit green
+ nblue >>= 3; // 5-bit blue
+ thispix = nblue + (ngreen << 5) + (nred << (5+6));
+
+ cnt1 = ny2pos - ny1pos;
+ nypos = ny1pos;
+ do {
+ ptr = (DATA*)(G.addr + (nypos * G.scr_var.xres + nx1pos) * BYTES_PER_PIXEL);
+ cnt2 = nx2pos - nx1pos;
+ do {
+ *ptr++ = thispix;
+ } while (--cnt2 >= 0);
+
+ nypos++;
+ } while (--cnt1 >= 0);
+}
+
+
+/**
+ * Draw a progress bar on framebuffer
+ * \param percent percentage of loading
+ */
+static void fb_drawprogressbar(unsigned percent)
+{
+ int i, left_x, top_y, width, height;
+
+ // outer box
+ left_x = G.nbar_posx;
+ top_y = G.nbar_posy;
+ width = G.nbar_width - 1;
+ height = G.nbar_height - 1;
+ if ((height | width) < 0)
+ return;
+ // NB: "width" of 1 actually makes rect with width of 2!
+ fb_drawrectangle();
+
+ // inner "empty" rectangle
+ left_x++;
+ top_y++;
+ width -= 2;
+ height -= 2;
+ if ((height | width) < 0)
+ return;
+ fb_drawfullrectangle(
+ left_x, top_y,
+ left_x + width, top_y + height,
+ G.nbar_colr, G.nbar_colg, G.nbar_colb);
+
+ if (percent > 0) {
+ // actual progress bar
+ width = width * percent / 100;
+ i = height;
+ if (height == 0)
+ height++; // divide by 0 is bad
+ while (i >= 0) {
+ // draw one-line thick "rectangle"
+ // top line will have gray lvl 200, bottom one 100
+ unsigned gray_level = 100 + i*100/height;
+ fb_drawfullrectangle(
+ left_x, top_y, left_x + width, top_y,
+ gray_level, gray_level, gray_level);
+ top_y++;
+ i--;
+ }
+ }
+}
+
+
+/**
+ * Draw image from PPM file
+ */
+static void fb_drawimage(void)
+{
+ char head[256];
+ char s[80];
+ FILE *theme_file;
+ unsigned char *pixline;
+ unsigned i, j, width, height, line_size;
+
+ memset(head, 0, sizeof(head));
+ theme_file = xfopen_stdin(G.image_filename);
+
+ // parse ppm header
+ while (1) {
+ if (fgets(s, sizeof(s), theme_file) == NULL)
+ bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+
+ if (s[0] == '#')
+ continue;
+
+ if (strlen(head) + strlen(s) >= sizeof(head))
+ bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+
+ strcat(head, s);
+ if (head[0] != 'P' || head[1] != '6')
+ bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+
+ // width, height, max_color_val
+ if (sscanf(head, "P6 %u %u %u", &width, &height, &i) == 3)
+ break;
+// TODO: i must be <= 255!
+ }
+
+ line_size = width*3;
+ if (width > G.scr_var.xres)
+ width = G.scr_var.xres;
+ if (height > G.scr_var.yres)
+ height = G.scr_var.yres;
+
+ pixline = xmalloc(line_size);
+ for (j = 0; j < height; j++) {
+ unsigned char *pixel = pixline;
+ DATA *src = (DATA *)(G.addr + j * G.scr_fix.line_length);
+
+ if (fread(pixline, 1, line_size, theme_file) != line_size)
+ bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+ for (i = 0; i < width; i++) {
+ unsigned thispix;
+ thispix = (((unsigned)pixel[0] << 8) & 0xf800)
+ | (((unsigned)pixel[1] << 3) & 0x07e0)
+ | (((unsigned)pixel[2] >> 3));
+ *src++ = thispix;
+ pixel += 3;
+ }
+ }
+ free(pixline);
+ fclose(theme_file);
+}
+
+
+/**
+ * Parse configuration file
+ * \param *cfg_filename name of the configuration file
+ */
+static void init(const char *cfg_filename)
+{
+ static const char const param_names[] ALIGN1 =
+ "BAR_WIDTH\0" "BAR_HEIGHT\0"
+ "BAR_LEFT\0" "BAR_TOP\0"
+ "BAR_R\0" "BAR_G\0" "BAR_B\0"
+#if DEBUG
+ "DEBUG\0"
+#endif
+ ;
+ char *token[2];
+ parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
+ while (config_read(parser, token, 2, 2, "#=",
+ (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
+ unsigned val = xatoi_u(token[1]);
+ int i = index_in_strings(param_names, token[0]);
+ if (i < 0)
+ bb_error_msg_and_die("syntax error: '%s'", token[0]);
+ if (i >= 0 && i < 7)
+ G.ns[i] = val;
+#if DEBUG
+ if (i == 7) {
+ G.bdebug_messages = val;
+ if (G.bdebug_messages)
+ G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
+ }
+#endif
+ }
+ config_close(parser);
+}
+
+
+int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fbsplash_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *fb_device, *cfg_filename, *fifo_filename;
+ FILE *fp = fp; // for compiler
+ char *num_buf;
+ unsigned num;
+ bool bCursorOff;
+
+ INIT_G();
+
+ // parse command line options
+ fb_device = "/dev/fb0";
+ cfg_filename = NULL;
+ fifo_filename = NULL;
+ bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
+ &G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
+
+ // parse configuration file
+ if (cfg_filename)
+ init(cfg_filename);
+
+ // We must have -s IMG
+ if (!G.image_filename)
+ bb_show_usage();
+
+ fb_open(fb_device);
+
+ if (fifo_filename && bCursorOff) {
+ // hide cursor (BEFORE any fb ops)
+ full_write(STDOUT_FILENO, "\x1b" "[?25l", 6);
+ }
+
+ fb_drawimage();
+
+ if (!fifo_filename)
+ return EXIT_SUCCESS;
+
+ fp = xfopen_stdin(fifo_filename);
+ if (fp != stdin) {
+ // For named pipes, we want to support this:
+ // mkfifo cmd_pipe
+ // fbsplash -f cmd_pipe .... &
+ // ...
+ // echo 33 >cmd_pipe
+ // ...
+ // echo 66 >cmd_pipe
+ // This means that we don't want fbsplash to get EOF
+ // when last writer closes input end.
+ // The simplest way is to open fifo for writing too
+ // and become an additional writer :)
+ open(fifo_filename, O_WRONLY); // errors are ignored
+ }
+
+ fb_drawprogressbar(0);
+ // Block on read, waiting for some input.
+ // Use of <stdio.h> style I/O allows to correctly
+ // handle a case when we have many buffered lines
+ // already in the pipe
+ while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
+ if (strncmp(num_buf, "exit", 4) == 0) {
+ DEBUG_MESSAGE("exit");
+ break;
+ }
+ num = atoi(num_buf);
+ if (isdigit(num_buf[0]) && (num <= 100)) {
+#if DEBUG
+ char strVal[10];
+ sprintf(strVal, "%d", num);
+ DEBUG_MESSAGE(strVal);
+#endif
+ fb_drawprogressbar(num);
+ }
+ free(num_buf);
+ }
+
+ if (bCursorOff) // restore cursor
+ full_write(STDOUT_FILENO, "\x1b" "[?25h", 6);
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/fbsplash.cfg b/miscutils/fbsplash.cfg
new file mode 100644
index 0000000..b6cf607
--- /dev/null
+++ b/miscutils/fbsplash.cfg
@@ -0,0 +1,9 @@
+# progress bar position
+BAR_LEFT=170
+BAR_TOP=300
+BAR_WIDTH=300
+BAR_HEIGHT=20
+# progress bar color
+BAR_R=80
+BAR_G=80
+BAR_B=130
diff --git a/miscutils/hdparm.c b/miscutils/hdparm.c
new file mode 100644
index 0000000..5c1f6d5
--- /dev/null
+++ b/miscutils/hdparm.c
@@ -0,0 +1,2063 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * hdparm implementation for busybox
+ *
+ * Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it>
+ * Hacked by Tito <farmatito@tiscali.it> for size optimization.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * This program is based on the source code of hdparm: see below...
+ * hdparm.c - Command line interface to get/set hard disk parameters
+ * - by Mark Lord (C) 1994-2002 -- freely distributable
+ */
+
+#include "libbb.h"
+#include <linux/hdreg.h>
+
+/* device types */
+/* ------------ */
+#define NO_DEV 0xffff
+#define ATA_DEV 0x0000
+#define ATAPI_DEV 0x0001
+
+/* word definitions */
+/* ---------------- */
+#define GEN_CONFIG 0 /* general configuration */
+#define LCYLS 1 /* number of logical cylinders */
+#define CONFIG 2 /* specific configuration */
+#define LHEADS 3 /* number of logical heads */
+#define TRACK_BYTES 4 /* number of bytes/track (ATA-1) */
+#define SECT_BYTES 5 /* number of bytes/sector (ATA-1) */
+#define LSECTS 6 /* number of logical sectors/track */
+#define START_SERIAL 10 /* ASCII serial number */
+#define LENGTH_SERIAL 10 /* 10 words (20 bytes or characters) */
+#define BUF_TYPE 20 /* buffer type (ATA-1) */
+#define BUFFER__SIZE 21 /* buffer size (ATA-1) */
+#define RW_LONG 22 /* extra bytes in R/W LONG cmd ( < ATA-4)*/
+#define START_FW_REV 23 /* ASCII firmware revision */
+#define LENGTH_FW_REV 4 /* 4 words (8 bytes or characters) */
+#define START_MODEL 27 /* ASCII model number */
+#define LENGTH_MODEL 20 /* 20 words (40 bytes or characters) */
+#define SECTOR_XFER_MAX 47 /* r/w multiple: max sectors xfered */
+#define DWORD_IO 48 /* can do double-word IO (ATA-1 only) */
+#define CAPAB_0 49 /* capabilities */
+#define CAPAB_1 50
+#define PIO_MODE 51 /* max PIO mode supported (obsolete)*/
+#define DMA_MODE 52 /* max Singleword DMA mode supported (obs)*/
+#define WHATS_VALID 53 /* what fields are valid */
+#define LCYLS_CUR 54 /* current logical cylinders */
+#define LHEADS_CUR 55 /* current logical heads */
+#define LSECTS_CUR 56 /* current logical sectors/track */
+#define CAPACITY_LSB 57 /* current capacity in sectors */
+#define CAPACITY_MSB 58
+#define SECTOR_XFER_CUR 59 /* r/w multiple: current sectors xfered */
+#define LBA_SECTS_LSB 60 /* LBA: total number of user */
+#define LBA_SECTS_MSB 61 /* addressable sectors */
+#define SINGLE_DMA 62 /* singleword DMA modes */
+#define MULTI_DMA 63 /* multiword DMA modes */
+#define ADV_PIO_MODES 64 /* advanced PIO modes supported */
+ /* multiword DMA xfer cycle time: */
+#define DMA_TIME_MIN 65 /* - minimum */
+#define DMA_TIME_NORM 66 /* - manufacturer's recommended */
+ /* minimum PIO xfer cycle time: */
+#define PIO_NO_FLOW 67 /* - without flow control */
+#define PIO_FLOW 68 /* - with IORDY flow control */
+#define PKT_REL 71 /* typical #ns from PKT cmd to bus rel */
+#define SVC_NBSY 72 /* typical #ns from SERVICE cmd to !BSY */
+#define CDR_MAJOR 73 /* CD ROM: major version number */
+#define CDR_MINOR 74 /* CD ROM: minor version number */
+#define QUEUE_DEPTH 75 /* queue depth */
+#define MAJOR 80 /* major version number */
+#define MINOR 81 /* minor version number */
+#define CMDS_SUPP_0 82 /* command/feature set(s) supported */
+#define CMDS_SUPP_1 83
+#define CMDS_SUPP_2 84
+#define CMDS_EN_0 85 /* command/feature set(s) enabled */
+#define CMDS_EN_1 86
+#define CMDS_EN_2 87
+#define ULTRA_DMA 88 /* ultra DMA modes */
+ /* time to complete security erase */
+#define ERASE_TIME 89 /* - ordinary */
+#define ENH_ERASE_TIME 90 /* - enhanced */
+#define ADV_PWR 91 /* current advanced power management level
+ in low byte, 0x40 in high byte. */
+#define PSWD_CODE 92 /* master password revision code */
+#define HWRST_RSLT 93 /* hardware reset result */
+#define ACOUSTIC 94 /* acoustic mgmt values ( >= ATA-6) */
+#define LBA_LSB 100 /* LBA: maximum. Currently only 48 */
+#define LBA_MID 101 /* bits are used, but addr 103 */
+#define LBA_48_MSB 102 /* has been reserved for LBA in */
+#define LBA_64_MSB 103 /* the future. */
+#define RM_STAT 127 /* removable media status notification feature set support */
+#define SECU_STATUS 128 /* security status */
+#define CFA_PWR_MODE 160 /* CFA power mode 1 */
+#define START_MEDIA 176 /* media serial number */
+#define LENGTH_MEDIA 20 /* 20 words (40 bytes or characters)*/
+#define START_MANUF 196 /* media manufacturer I.D. */
+#define LENGTH_MANUF 10 /* 10 words (20 bytes or characters) */
+#define INTEGRITY 255 /* integrity word */
+
+/* bit definitions within the words */
+/* -------------------------------- */
+
+/* many words are considered valid if bit 15 is 0 and bit 14 is 1 */
+#define VALID 0xc000
+#define VALID_VAL 0x4000
+/* many words are considered invalid if they are either all-0 or all-1 */
+#define NOVAL_0 0x0000
+#define NOVAL_1 0xffff
+
+/* word 0: gen_config */
+#define NOT_ATA 0x8000
+#define NOT_ATAPI 0x4000 /* (check only if bit 15 == 1) */
+#define MEDIA_REMOVABLE 0x0080
+#define DRIVE_NOT_REMOVABLE 0x0040 /* bit obsoleted in ATA 6 */
+#define INCOMPLETE 0x0004
+#define CFA_SUPPORT_VAL 0x848a /* 848a=CFA feature set support */
+#define DRQ_RESPONSE_TIME 0x0060
+#define DRQ_3MS_VAL 0x0000
+#define DRQ_INTR_VAL 0x0020
+#define DRQ_50US_VAL 0x0040
+#define PKT_SIZE_SUPPORTED 0x0003
+#define PKT_SIZE_12_VAL 0x0000
+#define PKT_SIZE_16_VAL 0x0001
+#define EQPT_TYPE 0x1f00
+#define SHIFT_EQPT 8
+
+#define CDROM 0x0005
+
+/* word 1: number of logical cylinders */
+#define LCYLS_MAX 0x3fff /* maximum allowable value */
+
+/* word 2: specific configuration
+ * (a) require SET FEATURES to spin-up
+ * (b) require spin-up to fully reply to IDENTIFY DEVICE
+ */
+#define STBY_NID_VAL 0x37c8 /* (a) and (b) */
+#define STBY_ID_VAL 0x738c /* (a) and not (b) */
+#define PWRD_NID_VAL 0x8c73 /* not (a) and (b) */
+#define PWRD_ID_VAL 0xc837 /* not (a) and not (b) */
+
+/* words 47 & 59: sector_xfer_max & sector_xfer_cur */
+#define SECTOR_XFER 0x00ff /* sectors xfered on r/w multiple cmds*/
+#define MULTIPLE_SETTING_VALID 0x0100 /* 1=multiple sector setting is valid */
+
+/* word 49: capabilities 0 */
+#define STD_STBY 0x2000 /* 1=standard values supported (ATA); 0=vendor specific values */
+#define IORDY_SUP 0x0800 /* 1=support; 0=may be supported */
+#define IORDY_OFF 0x0400 /* 1=may be disabled */
+#define LBA_SUP 0x0200 /* 1=Logical Block Address support */
+#define DMA_SUP 0x0100 /* 1=Direct Memory Access support */
+#define DMA_IL_SUP 0x8000 /* 1=interleaved DMA support (ATAPI) */
+#define CMD_Q_SUP 0x4000 /* 1=command queuing support (ATAPI) */
+#define OVLP_SUP 0x2000 /* 1=overlap operation support (ATAPI) */
+#define SWRST_REQ 0x1000 /* 1=ATA SW reset required (ATAPI, obsolete */
+
+/* word 50: capabilities 1 */
+#define MIN_STANDBY_TIMER 0x0001 /* 1=device specific standby timer value minimum */
+
+/* words 51 & 52: PIO & DMA cycle times */
+#define MODE 0xff00 /* the mode is in the MSBs */
+
+/* word 53: whats_valid */
+#define OK_W88 0x0004 /* the ultra_dma info is valid */
+#define OK_W64_70 0x0002 /* see above for word descriptions */
+#define OK_W54_58 0x0001 /* current cyl, head, sector, cap. info valid */
+
+/*word 63,88: dma_mode, ultra_dma_mode*/
+#define MODE_MAX 7 /* bit definitions force udma <=7 (when
+ * udma >=8 comes out it'll have to be
+ * defined in a new dma_mode word!) */
+
+/* word 64: PIO transfer modes */
+#define PIO_SUP 0x00ff /* only bits 0 & 1 are used so far, */
+#define PIO_MODE_MAX 8 /* but all 8 bits are defined */
+
+/* word 75: queue_depth */
+#define DEPTH_BITS 0x001f /* bits used for queue depth */
+
+/* words 80-81: version numbers */
+/* NOVAL_0 or NOVAL_1 means device does not report version */
+
+/* word 81: minor version number */
+#define MINOR_MAX 0x22
+/* words 82-84: cmds/feats supported */
+#define CMDS_W82 0x77ff /* word 82: defined command locations*/
+#define CMDS_W83 0x3fff /* word 83: defined command locations*/
+#define CMDS_W84 0x002f /* word 83: defined command locations*/
+#define SUPPORT_48_BIT 0x0400
+#define NUM_CMD_FEAT_STR 48
+
+/* words 85-87: cmds/feats enabled */
+/* use cmd_feat_str[] to display what commands and features have
+ * been enabled with words 85-87
+ */
+
+/* words 89, 90, SECU ERASE TIME */
+#define ERASE_BITS 0x00ff
+
+/* word 92: master password revision */
+/* NOVAL_0 or NOVAL_1 means no support for master password revision */
+
+/* word 93: hw reset result */
+#define CBLID 0x2000 /* CBLID status */
+#define RST0 0x0001 /* 1=reset to device #0 */
+#define DEV_DET 0x0006 /* how device num determined */
+#define JUMPER_VAL 0x0002 /* device num determined by jumper */
+#define CSEL_VAL 0x0004 /* device num determined by CSEL_VAL */
+
+/* word 127: removable media status notification feature set support */
+#define RM_STAT_BITS 0x0003
+#define RM_STAT_SUP 0x0001
+
+/* word 128: security */
+#define SECU_ENABLED 0x0002
+#define SECU_LEVEL 0x0010
+#define NUM_SECU_STR 6
+
+/* word 160: CFA power mode */
+#define VALID_W160 0x8000 /* 1=word valid */
+#define PWR_MODE_REQ 0x2000 /* 1=CFA power mode req'd by some cmds*/
+#define PWR_MODE_OFF 0x1000 /* 1=CFA power moded disabled */
+#define MAX_AMPS 0x0fff /* value = max current in ma */
+
+/* word 255: integrity */
+#define SIG 0x00ff /* signature location */
+#define SIG_VAL 0x00a5 /* signature value */
+
+#define TIMING_BUF_MB 1
+#define TIMING_BUF_BYTES (TIMING_BUF_MB * 1024 * 1024)
+
+#undef DO_FLUSHCACHE /* under construction: force cache flush on -W0 */
+
+
+enum { fd = 3 };
+
+
+struct globals {
+ smallint get_identity, get_geom;
+ smallint do_flush;
+ smallint do_ctimings, do_timings;
+ smallint reread_partn;
+ smallint set_piomode, noisy_piomode;
+ smallint set_readahead, get_readahead;
+ smallint set_readonly, get_readonly;
+ smallint set_unmask, get_unmask;
+ smallint set_mult, get_mult;
+#ifdef HDIO_GET_QDMA
+ smallint get_dma_q;
+#ifdef HDIO_SET_QDMA
+ smallint set_dma_q;
+#endif
+#endif
+ smallint set_nowerr, get_nowerr;
+ smallint set_keep, get_keep;
+ smallint set_io32bit, get_io32bit;
+ int piomode;
+ unsigned long Xreadahead;
+ unsigned long readonly;
+ unsigned long unmask;
+ unsigned long mult;
+#ifdef HDIO_SET_QDMA
+ unsigned long dma_q;
+#endif
+ unsigned long nowerr;
+ unsigned long keep;
+ unsigned long io32bit;
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+ unsigned long dma;
+ smallint set_dma, get_dma;
+#endif
+#ifdef HDIO_DRIVE_CMD
+ smallint set_xfermode, get_xfermode;
+ smallint set_dkeep, get_dkeep;
+ smallint set_standby, get_standby;
+ smallint set_lookahead, get_lookahead;
+ smallint set_prefetch, get_prefetch;
+ smallint set_defects, get_defects;
+ smallint set_wcache, get_wcache;
+ smallint set_doorlock, get_doorlock;
+ smallint set_seagate, get_seagate;
+ smallint set_standbynow, get_standbynow;
+ smallint set_sleepnow, get_sleepnow;
+ smallint get_powermode;
+ smallint set_apmmode, get_apmmode;
+ int xfermode_requested;
+ unsigned long dkeep;
+ unsigned long standby_requested; /* 0..255 */
+ unsigned long lookahead;
+ unsigned long prefetch;
+ unsigned long defects;
+ unsigned long wcache;
+ unsigned long doorlock;
+ unsigned long apmmode;
+#endif
+ USE_FEATURE_HDPARM_GET_IDENTITY( smallint get_IDentity;)
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF( smallint set_busstate, get_busstate;)
+ USE_FEATURE_HDPARM_HDIO_DRIVE_RESET( smallint perform_reset;)
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF( smallint perform_tristate;)
+ USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(smallint unregister_hwif;)
+ USE_FEATURE_HDPARM_HDIO_SCAN_HWIF( smallint scan_hwif;)
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF( unsigned long busstate;)
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF( unsigned long tristate;)
+ USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(unsigned long hwif;)
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+ unsigned long hwif_data;
+ unsigned long hwif_ctrl;
+ unsigned long hwif_irq;
+#endif
+#ifdef DO_FLUSHCACHE
+ unsigned char flushcache[4] = { WIN_FLUSHCACHE, 0, 0, 0 };
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define get_identity (G.get_identity )
+#define get_geom (G.get_geom )
+#define do_flush (G.do_flush )
+#define do_ctimings (G.do_ctimings )
+#define do_timings (G.do_timings )
+#define reread_partn (G.reread_partn )
+#define set_piomode (G.set_piomode )
+#define noisy_piomode (G.noisy_piomode )
+#define set_readahead (G.set_readahead )
+#define get_readahead (G.get_readahead )
+#define set_readonly (G.set_readonly )
+#define get_readonly (G.get_readonly )
+#define set_unmask (G.set_unmask )
+#define get_unmask (G.get_unmask )
+#define set_mult (G.set_mult )
+#define get_mult (G.get_mult )
+#define set_dma_q (G.set_dma_q )
+#define get_dma_q (G.get_dma_q )
+#define set_nowerr (G.set_nowerr )
+#define get_nowerr (G.get_nowerr )
+#define set_keep (G.set_keep )
+#define get_keep (G.get_keep )
+#define set_io32bit (G.set_io32bit )
+#define get_io32bit (G.get_io32bit )
+#define piomode (G.piomode )
+#define Xreadahead (G.Xreadahead )
+#define readonly (G.readonly )
+#define unmask (G.unmask )
+#define mult (G.mult )
+#define dma_q (G.dma_q )
+#define nowerr (G.nowerr )
+#define keep (G.keep )
+#define io32bit (G.io32bit )
+#define dma (G.dma )
+#define set_dma (G.set_dma )
+#define get_dma (G.get_dma )
+#define set_xfermode (G.set_xfermode )
+#define get_xfermode (G.get_xfermode )
+#define set_dkeep (G.set_dkeep )
+#define get_dkeep (G.get_dkeep )
+#define set_standby (G.set_standby )
+#define get_standby (G.get_standby )
+#define set_lookahead (G.set_lookahead )
+#define get_lookahead (G.get_lookahead )
+#define set_prefetch (G.set_prefetch )
+#define get_prefetch (G.get_prefetch )
+#define set_defects (G.set_defects )
+#define get_defects (G.get_defects )
+#define set_wcache (G.set_wcache )
+#define get_wcache (G.get_wcache )
+#define set_doorlock (G.set_doorlock )
+#define get_doorlock (G.get_doorlock )
+#define set_seagate (G.set_seagate )
+#define get_seagate (G.get_seagate )
+#define set_standbynow (G.set_standbynow )
+#define get_standbynow (G.get_standbynow )
+#define set_sleepnow (G.set_sleepnow )
+#define get_sleepnow (G.get_sleepnow )
+#define get_powermode (G.get_powermode )
+#define set_apmmode (G.set_apmmode )
+#define get_apmmode (G.get_apmmode )
+#define xfermode_requested (G.xfermode_requested )
+#define dkeep (G.dkeep )
+#define standby_requested (G.standby_requested )
+#define lookahead (G.lookahead )
+#define prefetch (G.prefetch )
+#define defects (G.defects )
+#define wcache (G.wcache )
+#define doorlock (G.doorlock )
+#define apmmode (G.apmmode )
+#define get_IDentity (G.get_IDentity )
+#define set_busstate (G.set_busstate )
+#define get_busstate (G.get_busstate )
+#define perform_reset (G.perform_reset )
+#define perform_tristate (G.perform_tristate )
+#define unregister_hwif (G.unregister_hwif )
+#define scan_hwif (G.scan_hwif )
+#define busstate (G.busstate )
+#define tristate (G.tristate )
+#define hwif (G.hwif )
+#define hwif_data (G.hwif_data )
+#define hwif_ctrl (G.hwif_ctrl )
+#define hwif_irq (G.hwif_irq )
+
+
+/* Busybox messages and functions */
+#if ENABLE_IOCTL_HEX2STR_ERROR
+static int ioctl_alt_func(/*int fd,*/ int cmd, unsigned char *args, int alt, const char *string)
+{
+ if (!ioctl(fd, cmd, args))
+ return 0;
+ args[0] = alt;
+ return bb_ioctl_or_warn(fd, cmd, args, string);
+}
+#define ioctl_alt_or_warn(cmd,args,alt) ioctl_alt_func(cmd,args,alt,#cmd)
+#else
+static int ioctl_alt_func(/*int fd,*/ int cmd, unsigned char *args, int alt)
+{
+ if (!ioctl(fd, cmd, args))
+ return 0;
+ args[0] = alt;
+ return bb_ioctl_or_warn(fd, cmd, args);
+}
+#define ioctl_alt_or_warn(cmd,args,alt) ioctl_alt_func(cmd,args,alt)
+#endif
+
+static void on_off(int value)
+{
+ puts(value ? " (on)" : " (off)");
+}
+
+static void print_flag_on_off(int get_arg, const char *s, unsigned long arg)
+{
+ if (get_arg) {
+ printf(" setting %s to %ld", s, arg);
+ on_off(arg);
+ }
+}
+
+static void print_value_on_off(const char *str, unsigned long argp)
+{
+ printf(" %s\t= %2ld", str, argp);
+ on_off(argp != 0);
+}
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static void print_ascii(const char *p, int length)
+{
+#if BB_BIG_ENDIAN
+#define LE_ONLY(x)
+ enum { ofs = 0 };
+#else
+#define LE_ONLY(x) x
+ /* every 16bit word is big-endian (i.e. inverted) */
+ /* accessing bytes in 1,0, 3,2, 5,4... sequence */
+ int ofs = 1;
+#endif
+
+ length *= 2;
+ /* find first non-space & print it */
+ while (length && p[ofs] != ' ') {
+ p++;
+ LE_ONLY(ofs = -ofs;)
+ length--;
+ }
+ while (length && p[ofs]) {
+ bb_putchar(p[ofs]);
+ p++;
+ LE_ONLY(ofs = -ofs;)
+ length--;
+ }
+ bb_putchar('\n');
+#undef LE_ONLY
+}
+
+static void xprint_ascii(uint16_t *val, int i, const char *string, int n)
+{
+ if (val[i]) {
+ printf("\t%-20s", string);
+ print_ascii((void*)&val[i], n);
+ }
+}
+
+static uint8_t mode_loop(uint16_t mode_sup, uint16_t mode_sel, int cc, uint8_t *have_mode)
+{
+ uint16_t ii;
+ uint8_t err_dma = 0;
+
+ for (ii = 0; ii <= MODE_MAX; ii++) {
+ if (mode_sel & 0x0001) {
+ printf("*%cdma%u ", cc, ii);
+ if (*have_mode)
+ err_dma = 1;
+ *have_mode = 1;
+ } else if (mode_sup & 0x0001)
+ printf("%cdma%u ", cc, ii);
+
+ mode_sup >>= 1;
+ mode_sel >>= 1;
+ }
+ return err_dma;
+}
+
+static const char pkt_str[] ALIGN1 =
+ "Direct-access device" "\0" /* word 0, bits 12-8 = 00 */
+ "Sequential-access device" "\0" /* word 0, bits 12-8 = 01 */
+ "Printer" "\0" /* word 0, bits 12-8 = 02 */
+ "Processor" "\0" /* word 0, bits 12-8 = 03 */
+ "Write-once device" "\0" /* word 0, bits 12-8 = 04 */
+ "CD-ROM" "\0" /* word 0, bits 12-8 = 05 */
+ "Scanner" "\0" /* word 0, bits 12-8 = 06 */
+ "Optical memory" "\0" /* word 0, bits 12-8 = 07 */
+ "Medium changer" "\0" /* word 0, bits 12-8 = 08 */
+ "Communications device" "\0" /* word 0, bits 12-8 = 09 */
+ "ACS-IT8 device" "\0" /* word 0, bits 12-8 = 0a */
+ "ACS-IT8 device" "\0" /* word 0, bits 12-8 = 0b */
+ "Array controller" "\0" /* word 0, bits 12-8 = 0c */
+ "Enclosure services" "\0" /* word 0, bits 12-8 = 0d */
+ "Reduced block command device" "\0" /* word 0, bits 12-8 = 0e */
+ "Optical card reader/writer" "\0" /* word 0, bits 12-8 = 0f */
+;
+
+static const char ata1_cfg_str[] ALIGN1 = /* word 0 in ATA-1 mode */
+ "reserved" "\0" /* bit 0 */
+ "hard sectored" "\0" /* bit 1 */
+ "soft sectored" "\0" /* bit 2 */
+ "not MFM encoded " "\0" /* bit 3 */
+ "head switch time > 15us" "\0" /* bit 4 */
+ "spindle motor control option" "\0" /* bit 5 */
+ "fixed drive" "\0" /* bit 6 */
+ "removable drive" "\0" /* bit 7 */
+ "disk xfer rate <= 5Mbs" "\0" /* bit 8 */
+ "disk xfer rate > 5Mbs, <= 10Mbs" "\0" /* bit 9 */
+ "disk xfer rate > 5Mbs" "\0" /* bit 10 */
+ "rotational speed tol." "\0" /* bit 11 */
+ "data strobe offset option" "\0" /* bit 12 */
+ "track offset option" "\0" /* bit 13 */
+ "format speed tolerance gap reqd" "\0" /* bit 14 */
+ "ATAPI" /* bit 14 */
+;
+
+static const char minor_str[] ALIGN1 =
+ /* word 81 value: */
+ "Unspecified" "\0" /* 0x0000 */
+ "ATA-1 X3T9.2 781D prior to rev.4" "\0" /* 0x0001 */
+ "ATA-1 published, ANSI X3.221-1994" "\0" /* 0x0002 */
+ "ATA-1 X3T9.2 781D rev.4" "\0" /* 0x0003 */
+ "ATA-2 published, ANSI X3.279-1996" "\0" /* 0x0004 */
+ "ATA-2 X3T10 948D prior to rev.2k" "\0" /* 0x0005 */
+ "ATA-3 X3T10 2008D rev.1" "\0" /* 0x0006 */
+ "ATA-2 X3T10 948D rev.2k" "\0" /* 0x0007 */
+ "ATA-3 X3T10 2008D rev.0" "\0" /* 0x0008 */
+ "ATA-2 X3T10 948D rev.3" "\0" /* 0x0009 */
+ "ATA-3 published, ANSI X3.298-199x" "\0" /* 0x000a */
+ "ATA-3 X3T10 2008D rev.6" "\0" /* 0x000b */
+ "ATA-3 X3T13 2008D rev.7 and 7a" "\0" /* 0x000c */
+ "ATA/ATAPI-4 X3T13 1153D rev.6" "\0" /* 0x000d */
+ "ATA/ATAPI-4 T13 1153D rev.13" "\0" /* 0x000e */
+ "ATA/ATAPI-4 X3T13 1153D rev.7" "\0" /* 0x000f */
+ "ATA/ATAPI-4 T13 1153D rev.18" "\0" /* 0x0010 */
+ "ATA/ATAPI-4 T13 1153D rev.15" "\0" /* 0x0011 */
+ "ATA/ATAPI-4 published, ANSI INCITS 317-1998" "\0" /* 0x0012 */
+ "ATA/ATAPI-5 T13 1321D rev.3" "\0" /* 0x0013 */
+ "ATA/ATAPI-4 T13 1153D rev.14" "\0" /* 0x0014 */
+ "ATA/ATAPI-5 T13 1321D rev.1" "\0" /* 0x0015 */
+ "ATA/ATAPI-5 published, ANSI INCITS 340-2000" "\0" /* 0x0016 */
+ "ATA/ATAPI-4 T13 1153D rev.17" "\0" /* 0x0017 */
+ "ATA/ATAPI-6 T13 1410D rev.0" "\0" /* 0x0018 */
+ "ATA/ATAPI-6 T13 1410D rev.3a" "\0" /* 0x0019 */
+ "ATA/ATAPI-7 T13 1532D rev.1" "\0" /* 0x001a */
+ "ATA/ATAPI-6 T13 1410D rev.2" "\0" /* 0x001b */
+ "ATA/ATAPI-6 T13 1410D rev.1" "\0" /* 0x001c */
+ "ATA/ATAPI-7 published, ANSI INCITS 397-2005" "\0" /* 0x001d */
+ "ATA/ATAPI-7 T13 1532D rev.0" "\0" /* 0x001e */
+ "reserved" "\0" /* 0x001f */
+ "reserved" "\0" /* 0x0020 */
+ "ATA/ATAPI-7 T13 1532D rev.4a" "\0" /* 0x0021 */
+ "ATA/ATAPI-6 published, ANSI INCITS 361-2002" "\0" /* 0x0022 */
+ "reserved" /* 0x0023-0xfffe */
+;
+static const char actual_ver[MINOR_MAX + 2] ALIGN1 = {
+ /* word 81 value: */
+ 0, /* 0x0000 WARNING: actual_ver[] array */
+ 1, /* 0x0001 WARNING: corresponds */
+ 1, /* 0x0002 WARNING: *exactly* */
+ 1, /* 0x0003 WARNING: to the ATA/ */
+ 2, /* 0x0004 WARNING: ATAPI version */
+ 2, /* 0x0005 WARNING: listed in */
+ 3, /* 0x0006 WARNING: the */
+ 2, /* 0x0007 WARNING: minor_str */
+ 3, /* 0x0008 WARNING: array */
+ 2, /* 0x0009 WARNING: above. */
+ 3, /* 0x000a WARNING: */
+ 3, /* 0x000b WARNING: If you change */
+ 3, /* 0x000c WARNING: that one, */
+ 4, /* 0x000d WARNING: change this one */
+ 4, /* 0x000e WARNING: too!!! */
+ 4, /* 0x000f */
+ 4, /* 0x0010 */
+ 4, /* 0x0011 */
+ 4, /* 0x0012 */
+ 5, /* 0x0013 */
+ 4, /* 0x0014 */
+ 5, /* 0x0015 */
+ 5, /* 0x0016 */
+ 4, /* 0x0017 */
+ 6, /* 0x0018 */
+ 6, /* 0x0019 */
+ 7, /* 0x001a */
+ 6, /* 0x001b */
+ 6, /* 0x001c */
+ 7, /* 0x001d */
+ 7, /* 0x001e */
+ 0, /* 0x001f */
+ 0, /* 0x0020 */
+ 7, /* 0x0021 */
+ 6, /* 0x0022 */
+ 0 /* 0x0023-0xfffe */
+};
+
+static const char cmd_feat_str[] ALIGN1 =
+ "" "\0" /* word 82 bit 15: obsolete */
+ "NOP cmd" "\0" /* word 82 bit 14 */
+ "READ BUFFER cmd" "\0" /* word 82 bit 13 */
+ "WRITE BUFFER cmd" "\0" /* word 82 bit 12 */
+ "" "\0" /* word 82 bit 11: obsolete */
+ "Host Protected Area feature set" "\0" /* word 82 bit 10 */
+ "DEVICE RESET cmd" "\0" /* word 82 bit 9 */
+ "SERVICE interrupt" "\0" /* word 82 bit 8 */
+ "Release interrupt" "\0" /* word 82 bit 7 */
+ "Look-ahead" "\0" /* word 82 bit 6 */
+ "Write cache" "\0" /* word 82 bit 5 */
+ "PACKET command feature set" "\0" /* word 82 bit 4 */
+ "Power Management feature set" "\0" /* word 82 bit 3 */
+ "Removable Media feature set" "\0" /* word 82 bit 2 */
+ "Security Mode feature set" "\0" /* word 82 bit 1 */
+ "SMART feature set" "\0" /* word 82 bit 0 */
+ /* -------------- */
+ "" "\0" /* word 83 bit 15: !valid bit */
+ "" "\0" /* word 83 bit 14: valid bit */
+ "FLUSH CACHE EXT cmd" "\0" /* word 83 bit 13 */
+ "Mandatory FLUSH CACHE cmd " "\0" /* word 83 bit 12 */
+ "Device Configuration Overlay feature set " "\0"
+ "48-bit Address feature set " "\0" /* word 83 bit 10 */
+ "" "\0"
+ "SET MAX security extension" "\0" /* word 83 bit 8 */
+ "Address Offset Reserved Area Boot" "\0" /* word 83 bit 7 */
+ "SET FEATURES subcommand required to spinup after power up" "\0"
+ "Power-Up In Standby feature set" "\0" /* word 83 bit 5 */
+ "Removable Media Status Notification feature set" "\0"
+ "Adv. Power Management feature set" "\0" /* word 83 bit 3 */
+ "CFA feature set" "\0" /* word 83 bit 2 */
+ "READ/WRITE DMA QUEUED" "\0" /* word 83 bit 1 */
+ "DOWNLOAD MICROCODE cmd" "\0" /* word 83 bit 0 */
+ /* -------------- */
+ "" "\0" /* word 84 bit 15: !valid bit */
+ "" "\0" /* word 84 bit 14: valid bit */
+ "" "\0" /* word 84 bit 13: reserved */
+ "" "\0" /* word 84 bit 12: reserved */
+ "" "\0" /* word 84 bit 11: reserved */
+ "" "\0" /* word 84 bit 10: reserved */
+ "" "\0" /* word 84 bit 9: reserved */
+ "" "\0" /* word 84 bit 8: reserved */
+ "" "\0" /* word 84 bit 7: reserved */
+ "" "\0" /* word 84 bit 6: reserved */
+ "General Purpose Logging feature set" "\0" /* word 84 bit 5 */
+ "" "\0" /* word 84 bit 4: reserved */
+ "Media Card Pass Through Command feature set " "\0"
+ "Media serial number " "\0" /* word 84 bit 2 */
+ "SMART self-test " "\0" /* word 84 bit 1 */
+ "SMART error logging " /* word 84 bit 0 */
+;
+
+static const char secu_str[] ALIGN1 =
+ "supported" "\0" /* word 128, bit 0 */
+ "enabled" "\0" /* word 128, bit 1 */
+ "locked" "\0" /* word 128, bit 2 */
+ "frozen" "\0" /* word 128, bit 3 */
+ "expired: security count" "\0" /* word 128, bit 4 */
+ "supported: enhanced erase" /* word 128, bit 5 */
+;
+
+// Parse 512 byte disk identification block and print much crap.
+static void identify(uint16_t *val) NORETURN;
+static void identify(uint16_t *val)
+{
+ uint16_t ii, jj, kk;
+ uint16_t like_std = 1, std = 0, min_std = 0xffff;
+ uint16_t dev = NO_DEV, eqpt = NO_DEV;
+ uint8_t have_mode = 0, err_dma = 0;
+ uint8_t chksum = 0;
+ uint32_t ll, mm, nn, oo;
+ uint64_t bbbig; /* (:) */
+ const char *strng;
+#if BB_BIG_ENDIAN
+ uint16_t buf[256];
+
+ // Adjust for endianness
+ swab(val, buf, sizeof(buf));
+ val = buf;
+#endif
+ /* check if we recognise the device type */
+ bb_putchar('\n');
+ if (!(val[GEN_CONFIG] & NOT_ATA)) {
+ dev = ATA_DEV;
+ printf("ATA device, with ");
+ } else if (val[GEN_CONFIG]==CFA_SUPPORT_VAL) {
+ dev = ATA_DEV;
+ like_std = 4;
+ printf("CompactFlash ATA device, with ");
+ } else if (!(val[GEN_CONFIG] & NOT_ATAPI)) {
+ dev = ATAPI_DEV;
+ eqpt = (val[GEN_CONFIG] & EQPT_TYPE) >> SHIFT_EQPT;
+ printf("ATAPI %s, with ", eqpt <= 0xf ? nth_string(pkt_str, eqpt) : "unknown");
+ like_std = 3;
+ } else
+ /* "Unknown device type:\n\tbits 15&14 of general configuration word 0 both set to 1.\n" */
+ bb_error_msg_and_die("unknown device type");
+
+ printf("%sremovable media\n", !(val[GEN_CONFIG] & MEDIA_REMOVABLE) ? "non-" : "");
+ /* Info from the specific configuration word says whether or not the
+ * ID command completed correctly. It is only defined, however in
+ * ATA/ATAPI-5 & 6; it is reserved (value theoretically 0) in prior
+ * standards. Since the values allowed for this word are extremely
+ * specific, it should be safe to check it now, even though we don't
+ * know yet what standard this device is using.
+ */
+ if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL)
+ || (val[CONFIG]==PWRD_NID_VAL) || (val[CONFIG]==PWRD_ID_VAL)
+ ) {
+ like_std = 5;
+ if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL))
+ printf("powers-up in standby; SET FEATURES subcmd spins-up.\n");
+ if (((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==PWRD_NID_VAL)) && (val[GEN_CONFIG] & INCOMPLETE))
+ printf("\n\tWARNING: ID response incomplete.\n\tFollowing data may be incorrect.\n\n");
+ }
+
+ /* output the model and serial numbers and the fw revision */
+ xprint_ascii(val, START_MODEL, "Model Number:", LENGTH_MODEL);
+ xprint_ascii(val, START_SERIAL, "Serial Number:", LENGTH_SERIAL);
+ xprint_ascii(val, START_FW_REV, "Firmware Revision:", LENGTH_FW_REV);
+ xprint_ascii(val, START_MEDIA, "Media Serial Num:", LENGTH_MEDIA);
+ xprint_ascii(val, START_MANUF, "Media Manufacturer:", LENGTH_MANUF);
+
+ /* major & minor standards version number (Note: these words were not
+ * defined until ATA-3 & the CDROM std uses different words.) */
+ printf("Standards:");
+ if (eqpt != CDROM) {
+ if (val[MINOR] && (val[MINOR] <= MINOR_MAX)) {
+ if (like_std < 3) like_std = 3;
+ std = actual_ver[val[MINOR]];
+ if (std) printf("\n\tUsed: %s ", nth_string(minor_str, val[MINOR]));
+
+ }
+ /* looks like when they up-issue the std, they obsolete one;
+ * thus, only the newest 4 issues need be supported. (That's
+ * what "kk" and "min_std" are all about.) */
+ if (val[MAJOR] && (val[MAJOR] != NOVAL_1)) {
+ printf("\n\tSupported: ");
+ jj = val[MAJOR] << 1;
+ kk = like_std >4 ? like_std-4: 0;
+ for (ii = 14; (ii >0)&&(ii>kk); ii--) {
+ if (jj & 0x8000) {
+ printf("%u ", ii);
+ if (like_std < ii) {
+ like_std = ii;
+ kk = like_std >4 ? like_std-4: 0;
+ }
+ if (min_std > ii) min_std = ii;
+ }
+ jj <<= 1;
+ }
+ if (like_std < 3) like_std = 3;
+ }
+ /* Figure out what standard the device is using if it hasn't told
+ * us. If we know the std, check if the device is using any of
+ * the words from the next level up. It happens.
+ */
+ if (like_std < std) like_std = std;
+
+ if (((std == 5) || (!std && (like_std < 6))) &&
+ ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+ (( val[CMDS_SUPP_1] & CMDS_W83) > 0x00ff)) ||
+ ((( val[CMDS_SUPP_2] & VALID) == VALID_VAL) &&
+ ( val[CMDS_SUPP_2] & CMDS_W84) ) )
+ ) {
+ like_std = 6;
+ } else if (((std == 4) || (!std && (like_std < 5))) &&
+ ((((val[INTEGRITY] & SIG) == SIG_VAL) && !chksum) ||
+ (( val[HWRST_RSLT] & VALID) == VALID_VAL) ||
+ ((( val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+ (( val[CMDS_SUPP_1] & CMDS_W83) > 0x001f)) ) )
+ {
+ like_std = 5;
+ } else if (((std == 3) || (!std && (like_std < 4))) &&
+ ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+ ((( val[CMDS_SUPP_1] & CMDS_W83) > 0x0000) ||
+ (( val[CMDS_SUPP_0] & CMDS_W82) > 0x000f))) ||
+ (( val[CAPAB_1] & VALID) == VALID_VAL) ||
+ (( val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) ||
+ (( val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP) )
+ ) {
+ like_std = 4;
+ } else if (((std == 2) || (!std && (like_std < 3)))
+ && ((val[CMDS_SUPP_1] & VALID) == VALID_VAL)
+ ) {
+ like_std = 3;
+ } else if (((std == 1) || (!std && (like_std < 2))) &&
+ ((val[CAPAB_0] & (IORDY_SUP | IORDY_OFF)) ||
+ (val[WHATS_VALID] & OK_W64_70)) )
+ {
+ like_std = 2;
+ }
+
+ if (!std)
+ printf("\n\tLikely used: %u\n", like_std);
+ else if (like_std > std)
+ printf("& some of %u\n", like_std);
+ else
+ bb_putchar('\n');
+ } else {
+ /* TBD: do CDROM stuff more thoroughly. For now... */
+ kk = 0;
+ if (val[CDR_MINOR] == 9) {
+ kk = 1;
+ printf("\n\tUsed: ATAPI for CD-ROMs, SFF-8020i, r2.5");
+ }
+ if (val[CDR_MAJOR] && (val[CDR_MAJOR] !=NOVAL_1)) {
+ kk = 1;
+ printf("\n\tSupported: CD-ROM ATAPI");
+ jj = val[CDR_MAJOR] >> 1;
+ for (ii = 1; ii < 15; ii++) {
+ if (jj & 0x0001) printf("-%u ", ii);
+ jj >>= 1;
+ }
+ }
+ puts(kk ? "" : "\n\tLikely used CD-ROM ATAPI-1");
+ /* the cdrom stuff is more like ATA-2 than anything else, so: */
+ like_std = 2;
+ }
+
+ if (min_std == 0xffff)
+ min_std = like_std > 4 ? like_std - 3 : 1;
+
+ printf("Configuration:\n");
+ /* more info from the general configuration word */
+ if ((eqpt != CDROM) && (like_std == 1)) {
+ jj = val[GEN_CONFIG] >> 1;
+ for (ii = 1; ii < 15; ii++) {
+ if (jj & 0x0001)
+ printf("\t%s\n", nth_string(ata1_cfg_str, ii));
+ jj >>=1;
+ }
+ }
+ if (dev == ATAPI_DEV) {
+ if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_3MS_VAL)
+ strng = "3ms";
+ else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_INTR_VAL)
+ strng = "<=10ms with INTRQ";
+ else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_50US_VAL)
+ strng ="50us";
+ else
+ strng = "unknown";
+ printf("\tDRQ response: %s\n\tPacket size: ", strng); /* Data Request (DRQ) */
+
+ if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_12_VAL)
+ strng = "12 bytes";
+ else if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_16_VAL)
+ strng = "16 bytes";
+ else
+ strng = "unknown";
+ puts(strng);
+ } else {
+ /* addressing...CHS? See section 6.2 of ATA specs 4 or 5 */
+ ll = (uint32_t)val[LBA_SECTS_MSB] << 16 | val[LBA_SECTS_LSB];
+ mm = 0; bbbig = 0;
+ if ((ll > 0x00FBFC10) && (!val[LCYLS]))
+ printf("\tCHS addressing not supported\n");
+ else {
+ jj = val[WHATS_VALID] & OK_W54_58;
+ printf("\tLogical\t\tmax\tcurrent\n\tcylinders\t%u\t%u\n\theads\t\t%u\t%u\n\tsectors/track\t%u\t%u\n\t--\n",
+ val[LCYLS],jj?val[LCYLS_CUR]:0, val[LHEADS],jj?val[LHEADS_CUR]:0, val[LSECTS],jj?val[LSECTS_CUR]:0);
+
+ if ((min_std == 1) && (val[TRACK_BYTES] || val[SECT_BYTES]))
+ printf("\tbytes/track: %u\tbytes/sector: %u\n", val[TRACK_BYTES], val[SECT_BYTES]);
+
+ if (jj) {
+ mm = (uint32_t)val[CAPACITY_MSB] << 16 | val[CAPACITY_LSB];
+ if (like_std < 3) {
+ /* check Endian of capacity bytes */
+ nn = val[LCYLS_CUR] * val[LHEADS_CUR] * val[LSECTS_CUR];
+ oo = (uint32_t)val[CAPACITY_LSB] << 16 | val[CAPACITY_MSB];
+ if (abs(mm - nn) > abs(oo - nn))
+ mm = oo;
+ }
+ printf("\tCHS current addressable sectors:%11u\n", mm);
+ }
+ }
+ /* LBA addressing */
+ printf("\tLBA user addressable sectors:%11u\n", ll);
+ if (((val[CMDS_SUPP_1] & VALID) == VALID_VAL)
+ && (val[CMDS_SUPP_1] & SUPPORT_48_BIT)
+ ) {
+ bbbig = (uint64_t)val[LBA_64_MSB] << 48 |
+ (uint64_t)val[LBA_48_MSB] << 32 |
+ (uint64_t)val[LBA_MID] << 16 |
+ val[LBA_LSB];
+ printf("\tLBA48 user addressable sectors:%11"PRIu64"\n", bbbig);
+ }
+
+ if (!bbbig)
+ bbbig = (uint64_t)(ll>mm ? ll : mm); /* # 512 byte blocks */
+ printf("\tdevice size with M = 1024*1024: %11"PRIu64" MBytes\n", bbbig>>11);
+ bbbig = (bbbig << 9) / 1000000;
+ printf("\tdevice size with M = 1000*1000: %11"PRIu64" MBytes ", bbbig);
+
+ if (bbbig > 1000)
+ printf("(%"PRIu64" GB)\n", bbbig/1000);
+ else
+ bb_putchar('\n');
+ }
+
+ /* hw support of commands (capabilities) */
+ printf("Capabilities:\n\t");
+
+ if (dev == ATAPI_DEV) {
+ if (eqpt != CDROM && (val[CAPAB_0] & CMD_Q_SUP)) printf("Cmd queuing, ");
+ if (val[CAPAB_0] & OVLP_SUP) printf("Cmd overlap, ");
+ }
+ if (val[CAPAB_0] & LBA_SUP) printf("LBA, ");
+
+ if (like_std != 1) {
+ printf("IORDY%s(can%s be disabled)\n",
+ !(val[CAPAB_0] & IORDY_SUP) ? "(may be)" : "",
+ (val[CAPAB_0] & IORDY_OFF) ? "" :"not");
+ } else
+ printf("no IORDY\n");
+
+ if ((like_std == 1) && val[BUF_TYPE]) {
+ printf("\tBuffer type: %04x: %s%s\n", val[BUF_TYPE],
+ (val[BUF_TYPE] < 2) ? "single port, single-sector" : "dual port, multi-sector",
+ (val[BUF_TYPE] > 2) ? " with read caching ability" : "");
+ }
+
+ if ((min_std == 1) && (val[BUFFER__SIZE] && (val[BUFFER__SIZE] != NOVAL_1))) {
+ printf("\tBuffer size: %.1fkB\n", (float)val[BUFFER__SIZE]/2);
+ }
+ if ((min_std < 4) && (val[RW_LONG])) {
+ printf("\tbytes avail on r/w long: %u\n", val[RW_LONG]);
+ }
+ if ((eqpt != CDROM) && (like_std > 3)) {
+ printf("\tQueue depth: %u\n", (val[QUEUE_DEPTH] & DEPTH_BITS) + 1);
+ }
+
+ if (dev == ATA_DEV) {
+ if (like_std == 1)
+ printf("\tCan%s perform double-word IO\n", (!val[DWORD_IO]) ? "not" : "");
+ else {
+ printf("\tStandby timer values: spec'd by %s", (val[CAPAB_0] & STD_STBY) ? "Standard" : "Vendor");
+ if ((like_std > 3) && ((val[CAPAB_1] & VALID) == VALID_VAL))
+ printf(", %s device specific minimum\n", (val[CAPAB_1] & MIN_STANDBY_TIMER) ? "with" : "no");
+ else
+ bb_putchar('\n');
+ }
+ printf("\tR/W multiple sector transfer: ");
+ if ((like_std < 3) && !(val[SECTOR_XFER_MAX] & SECTOR_XFER))
+ printf("not supported\n");
+ else {
+ printf("Max = %u\tCurrent = ", val[SECTOR_XFER_MAX] & SECTOR_XFER);
+ if (val[SECTOR_XFER_CUR] & MULTIPLE_SETTING_VALID)
+ printf("%u\n", val[SECTOR_XFER_CUR] & SECTOR_XFER);
+ else
+ printf("?\n");
+ }
+ if ((like_std > 3) && (val[CMDS_SUPP_1] & 0x0008)) {
+ /* We print out elsewhere whether the APM feature is enabled or
+ not. If it's not enabled, let's not repeat the info; just print
+ nothing here. */
+ printf("\tAdvancedPM level: ");
+ if ((val[ADV_PWR] & 0xFF00) == 0x4000) {
+ uint8_t apm_level = val[ADV_PWR] & 0x00FF;
+ printf("%u (0x%x)\n", apm_level, apm_level);
+ }
+ else
+ printf("unknown setting (0x%04x)\n", val[ADV_PWR]);
+ }
+ if (like_std > 5 && val[ACOUSTIC]) {
+ printf("\tRecommended acoustic management value: %u, current value: %u\n",
+ (val[ACOUSTIC] >> 8) & 0x00ff, val[ACOUSTIC] & 0x00ff);
+ }
+ } else {
+ /* ATAPI */
+ if (eqpt != CDROM && (val[CAPAB_0] & SWRST_REQ))
+ printf("\tATA sw reset required\n");
+
+ if (val[PKT_REL] || val[SVC_NBSY]) {
+ printf("\tOverlap support:");
+ if (val[PKT_REL]) printf(" %uus to release bus.", val[PKT_REL]);
+ if (val[SVC_NBSY]) printf(" %uus to clear BSY after SERVICE cmd.", val[SVC_NBSY]);
+ bb_putchar('\n');
+ }
+ }
+
+ /* DMA stuff. Check that only one DMA mode is selected. */
+ printf("\tDMA: ");
+ if (!(val[CAPAB_0] & DMA_SUP))
+ printf("not supported\n");
+ else {
+ if (val[DMA_MODE] && !val[SINGLE_DMA] && !val[MULTI_DMA])
+ printf(" sdma%u\n", (val[DMA_MODE] & MODE) >> 8);
+ if (val[SINGLE_DMA]) {
+ jj = val[SINGLE_DMA];
+ kk = val[SINGLE_DMA] >> 8;
+ err_dma += mode_loop(jj, kk, 's', &have_mode);
+ }
+ if (val[MULTI_DMA]) {
+ jj = val[MULTI_DMA];
+ kk = val[MULTI_DMA] >> 8;
+ err_dma += mode_loop(jj, kk, 'm', &have_mode);
+ }
+ if ((val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) {
+ jj = val[ULTRA_DMA];
+ kk = val[ULTRA_DMA] >> 8;
+ err_dma += mode_loop(jj, kk, 'u', &have_mode);
+ }
+ if (err_dma || !have_mode) printf("(?)");
+ bb_putchar('\n');
+
+ if ((dev == ATAPI_DEV) && (eqpt != CDROM) && (val[CAPAB_0] & DMA_IL_SUP))
+ printf("\t\tInterleaved DMA support\n");
+
+ if ((val[WHATS_VALID] & OK_W64_70)
+ && (val[DMA_TIME_MIN] || val[DMA_TIME_NORM])
+ ) {
+ printf("\t\tCycle time:");
+ if (val[DMA_TIME_MIN]) printf(" min=%uns", val[DMA_TIME_MIN]);
+ if (val[DMA_TIME_NORM]) printf(" recommended=%uns", val[DMA_TIME_NORM]);
+ bb_putchar('\n');
+ }
+ }
+
+ /* Programmed IO stuff */
+ printf("\tPIO: ");
+ /* If a drive supports mode n (e.g. 3), it also supports all modes less
+ * than n (e.g. 3, 2, 1 and 0). Print all the modes. */
+ if ((val[WHATS_VALID] & OK_W64_70) && (val[ADV_PIO_MODES] & PIO_SUP)) {
+ jj = ((val[ADV_PIO_MODES] & PIO_SUP) << 3) | 0x0007;
+ for (ii = 0; ii <= PIO_MODE_MAX; ii++) {
+ if (jj & 0x0001) printf("pio%d ", ii);
+ jj >>=1;
+ }
+ bb_putchar('\n');
+ } else if (((min_std < 5) || (eqpt == CDROM)) && (val[PIO_MODE] & MODE)) {
+ for (ii = 0; ii <= val[PIO_MODE]>>8; ii++)
+ printf("pio%d ", ii);
+ bb_putchar('\n');
+ } else
+ puts("unknown");
+
+ if (val[WHATS_VALID] & OK_W64_70) {
+ if (val[PIO_NO_FLOW] || val[PIO_FLOW]) {
+ printf("\t\tCycle time:");
+ if (val[PIO_NO_FLOW]) printf(" no flow control=%uns", val[PIO_NO_FLOW]);
+ if (val[PIO_FLOW]) printf(" IORDY flow control=%uns", val[PIO_FLOW]);
+ bb_putchar('\n');
+ }
+ }
+
+ if ((val[CMDS_SUPP_1] & VALID) == VALID_VAL) {
+ printf("Commands/features:\n\tEnabled\tSupported:\n");
+ jj = val[CMDS_SUPP_0];
+ kk = val[CMDS_EN_0];
+ for (ii = 0; ii < NUM_CMD_FEAT_STR; ii++) {
+ const char *feat_str = nth_string(cmd_feat_str, ii);
+ if ((jj & 0x8000) && (*feat_str != '\0')) {
+ printf("\t%s\t%s\n", (kk & 0x8000) ? " *" : "", feat_str);
+ }
+ jj <<= 1;
+ kk <<= 1;
+ if (ii % 16 == 15) {
+ jj = val[CMDS_SUPP_0+1+(ii/16)];
+ kk = val[CMDS_EN_0+1+(ii/16)];
+ }
+ if (ii == 31) {
+ if ((val[CMDS_SUPP_2] & VALID) != VALID_VAL)
+ ii +=16;
+ }
+ }
+ }
+ /* Removable Media Status Notification feature set */
+ if ((val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP)
+ printf("\t%s supported\n", nth_string(cmd_feat_str, 27));
+
+ /* security */
+ if ((eqpt != CDROM) && (like_std > 3)
+ && (val[SECU_STATUS] || val[ERASE_TIME] || val[ENH_ERASE_TIME])
+ ) {
+ printf("Security:\n");
+ if (val[PSWD_CODE] && (val[PSWD_CODE] != NOVAL_1))
+ printf("\tMaster password revision code = %u\n", val[PSWD_CODE]);
+ jj = val[SECU_STATUS];
+ if (jj) {
+ for (ii = 0; ii < NUM_SECU_STR; ii++) {
+ printf("\t%s\t%s\n", (!(jj & 0x0001)) ? "not" : "", nth_string(secu_str, ii));
+ jj >>=1;
+ }
+ if (val[SECU_STATUS] & SECU_ENABLED) {
+ printf("\tSecurity level %s\n", (val[SECU_STATUS] & SECU_LEVEL) ? "maximum" : "high");
+ }
+ }
+ jj = val[ERASE_TIME] & ERASE_BITS;
+ kk = val[ENH_ERASE_TIME] & ERASE_BITS;
+ if (jj || kk) {
+ bb_putchar('\t');
+ if (jj) printf("%umin for %sSECURITY ERASE UNIT. ", jj==ERASE_BITS ? 508 : jj<<1, "");
+ if (kk) printf("%umin for %sSECURITY ERASE UNIT. ", kk==ERASE_BITS ? 508 : kk<<1, "ENHANCED ");
+ bb_putchar('\n');
+ }
+ }
+
+ /* reset result */
+ jj = val[HWRST_RSLT];
+ if ((jj & VALID) == VALID_VAL) {
+ oo = (jj & RST0);
+ if (!oo)
+ jj >>= 8;
+ if ((jj & DEV_DET) == JUMPER_VAL)
+ strng = " determined by the jumper";
+ else if ((jj & DEV_DET) == CSEL_VAL)
+ strng = " determined by CSEL";
+ else
+ strng = "";
+ printf("HW reset results:\n\tCBLID- %s Vih\n\tDevice num = %i%s\n",
+ (val[HWRST_RSLT] & CBLID) ? "above" : "below", !(oo), strng);
+ }
+
+ /* more stuff from std 5 */
+ if ((like_std > 4) && (eqpt != CDROM)) {
+ if (val[CFA_PWR_MODE] & VALID_W160) {
+ printf("CFA power mode 1:\n\t%s%s\n", (val[CFA_PWR_MODE] & PWR_MODE_OFF) ? "disabled" : "enabled",
+ (val[CFA_PWR_MODE] & PWR_MODE_REQ) ? " and required by some commands" : "");
+
+ if (val[CFA_PWR_MODE] & MAX_AMPS)
+ printf("\tMaximum current = %uma\n", val[CFA_PWR_MODE] & MAX_AMPS);
+ }
+ if ((val[INTEGRITY] & SIG) == SIG_VAL) {
+ printf("Checksum: %scorrect\n", chksum ? "in" : "");
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+#endif
+
+// Historically, if there was no HDIO_OBSOLETE_IDENTITY, then
+// then the HDIO_GET_IDENTITY only returned 142 bytes.
+// Otherwise, HDIO_OBSOLETE_IDENTITY returns 142 bytes,
+// and HDIO_GET_IDENTITY returns 512 bytes. But the latest
+// 2.5.xx kernels no longer define HDIO_OBSOLETE_IDENTITY
+// (which they should, but they should just return -EINVAL).
+//
+// So.. we must now assume that HDIO_GET_IDENTITY returns 512 bytes.
+// On a really old system, it will not, and we will be confused.
+// Too bad, really.
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static const char cfg_str[] ALIGN1 =
+ """\0" "HardSect""\0" "SoftSect""\0" "NotMFM""\0"
+ "HdSw>15uSec""\0" "SpinMotCtl""\0" "Fixed""\0" "Removeable""\0"
+ "DTR<=5Mbs""\0" "DTR>5Mbs""\0" "DTR>10Mbs""\0" "RotSpdTol>.5%""\0"
+ "dStbOff""\0" "TrkOff""\0" "FmtGapReq""\0" "nonMagnetic"
+;
+
+static const char BuffType[] ALIGN1 =
+ "unknown""\0" "1Sect""\0" "DualPort""\0" "DualPortCache"
+;
+
+static void dump_identity(const struct hd_driveid *id)
+{
+ int i;
+ const unsigned short *id_regs = (const void*) id;
+
+ printf("\n Model=%.40s, FwRev=%.8s, SerialNo=%.20s\n Config={",
+ id->model, id->fw_rev, id->serial_no);
+ for (i = 0; i <= 15; i++) {
+ if (id->config & (1<<i))
+ printf(" %s", nth_string(cfg_str, i));
+ }
+ printf(" }\n RawCHS=%u/%u/%u, TrkSize=%u, SectSize=%u, ECCbytes=%u\n"
+ " BuffType=(%u) %s, BuffSize=%ukB, MaxMultSect=%u",
+ id->cyls, id->heads, id->sectors, id->track_bytes,
+ id->sector_bytes, id->ecc_bytes,
+ id->buf_type, nth_string(BuffType, (id->buf_type > 3) ? 0 : id->buf_type),
+ id->buf_size/2, id->max_multsect);
+ if (id->max_multsect) {
+ printf(", MultSect=");
+ if (!(id->multsect_valid & 1))
+ printf("?%u?", id->multsect);
+ else if (id->multsect)
+ printf("%u", id->multsect);
+ else
+ printf("off");
+ }
+ bb_putchar('\n');
+
+ if (!(id->field_valid & 1))
+ printf(" (maybe):");
+
+ printf(" CurCHS=%u/%u/%u, CurSects=%lu, LBA=%s", id->cur_cyls, id->cur_heads,
+ id->cur_sectors,
+ (BB_BIG_ENDIAN) ?
+ (unsigned long)(id->cur_capacity0 << 16) | id->cur_capacity1 :
+ (unsigned long)(id->cur_capacity1 << 16) | id->cur_capacity0,
+ ((id->capability&2) == 0) ? "no" : "yes");
+
+ if (id->capability & 2)
+ printf(", LBAsects=%u", id->lba_capacity);
+
+ printf("\n IORDY=%s", (id->capability & 8) ? (id->capability & 4) ? "on/off" : "yes" : "no");
+
+ if (((id->capability & 8) || (id->field_valid & 2)) && (id->field_valid & 2))
+ printf(", tPIO={min:%u,w/IORDY:%u}", id->eide_pio, id->eide_pio_iordy);
+
+ if ((id->capability & 1) && (id->field_valid & 2))
+ printf(", tDMA={min:%u,rec:%u}", id->eide_dma_min, id->eide_dma_time);
+
+ printf("\n PIO modes: ");
+ if (id->tPIO <= 5) {
+ printf("pio0 ");
+ if (id->tPIO >= 1) printf("pio1 ");
+ if (id->tPIO >= 2) printf("pio2 ");
+ }
+ if (id->field_valid & 2) {
+ static const masks_labels_t pio_modes = {
+ .masks = { 1, 2, ~3 },
+ .labels = "pio3 \0""pio4 \0""pio? \0",
+ };
+ print_flags(&pio_modes, id->eide_pio_modes);
+ }
+ if (id->capability & 1) {
+ if (id->dma_1word | id->dma_mword) {
+ static const int dma_wmode_masks[] = { 0x100, 1, 0x200, 2, 0x400, 4, 0xf800, 0xf8 };
+ printf("\n DMA modes: ");
+ print_flags_separated(dma_wmode_masks,
+ "*\0""sdma0 \0""*\0""sdma1 \0""*\0""sdma2 \0""*\0""sdma? \0",
+ id->dma_1word, NULL);
+ print_flags_separated(dma_wmode_masks,
+ "*\0""mdma0\0""*\0""mdma1\0""*\0""mdma2\0""*\0""mdma?\0",
+ id->dma_mword, NULL);
+ }
+ }
+ if (((id->capability & 8) || (id->field_valid & 2)) && id->field_valid & 4) {
+ static const masks_labels_t ultra_modes1 = {
+ .masks = { 0x100, 0x001, 0x200, 0x002, 0x400, 0x004 },
+ .labels = "*\0""udma0 \0""*\0""udma1 \0""*\0""udma2 \0",
+ };
+
+ printf("\n UDMA modes: ");
+ print_flags(&ultra_modes1, id->dma_ultra);
+#ifdef __NEW_HD_DRIVE_ID
+ if (id->hw_config & 0x2000) {
+#else /* !__NEW_HD_DRIVE_ID */
+ if (id->word93 & 0x2000) {
+#endif /* __NEW_HD_DRIVE_ID */
+ static const masks_labels_t ultra_modes2 = {
+ .masks = { 0x0800, 0x0008, 0x1000, 0x0010,
+ 0x2000, 0x0020, 0x4000, 0x0040,
+ 0x8000, 0x0080 },
+ .labels = "*\0""udma3 \0""*\0""udma4 \0"
+ "*\0""udma5 \0""*\0""udma6 \0"
+ "*\0""udma7 \0"
+ };
+ print_flags(&ultra_modes2, id->dma_ultra);
+ }
+ }
+ printf("\n AdvancedPM=%s", (!(id_regs[83] & 8)) ? "no" : "yes");
+ if (id_regs[83] & 8) {
+ if (!(id_regs[86] & 8))
+ printf(": disabled (255)");
+ else if ((id_regs[91] & 0xFF00) != 0x4000)
+ printf(": unknown setting");
+ else
+ printf(": mode=0x%02X (%u)", id_regs[91] & 0xFF, id_regs[91] & 0xFF);
+ }
+ if (id_regs[82] & 0x20)
+ printf(" WriteCache=%s", (id_regs[85] & 0x20) ? "enabled" : "disabled");
+#ifdef __NEW_HD_DRIVE_ID
+ if ((id->minor_rev_num && id->minor_rev_num <= 31)
+ || (id->major_rev_num && id->minor_rev_num <= 31)
+ ) {
+ printf("\n Drive conforms to: %s: ", (id->minor_rev_num <= 31) ? nth_string(minor_str, id->minor_rev_num) : "unknown");
+ if (id->major_rev_num != 0x0000 && /* NOVAL_0 */
+ id->major_rev_num != 0xFFFF) { /* NOVAL_1 */
+ for (i = 0; i <= 15; i++) {
+ if (id->major_rev_num & (1<<i))
+ printf(" ATA/ATAPI-%u", i);
+ }
+ }
+ }
+#endif /* __NEW_HD_DRIVE_ID */
+ printf("\n\n * current active mode\n\n");
+}
+#endif
+
+static void flush_buffer_cache(/*int fd*/ void)
+{
+ fsync(fd); /* flush buffers */
+ ioctl_or_warn(fd, BLKFLSBUF, NULL); /* do it again, big time */
+#ifdef HDIO_DRIVE_CMD
+ sleep(1);
+ if (ioctl(fd, HDIO_DRIVE_CMD, NULL) && errno != EINVAL) { /* await completion */
+ if (ENABLE_IOCTL_HEX2STR_ERROR) /* To be coherent with ioctl_or_warn */
+ bb_perror_msg("HDIO_DRIVE_CMD");
+ else
+ bb_perror_msg("ioctl %#x failed", HDIO_DRIVE_CMD);
+ }
+#endif
+}
+
+static void seek_to_zero(/*int fd*/ void)
+{
+ xlseek(fd, (off_t) 0, SEEK_SET);
+}
+
+static void read_big_block(/*int fd,*/ char *buf)
+{
+ int i;
+
+ xread(fd, buf, TIMING_BUF_BYTES);
+ /* access all sectors of buf to ensure the read fully completed */
+ for (i = 0; i < TIMING_BUF_BYTES; i += 512)
+ buf[i] &= 1;
+}
+
+static unsigned dev_size_mb(/*int fd*/ void)
+{
+ union {
+ unsigned long long blksize64;
+ unsigned blksize32;
+ } u;
+
+ if (0 == ioctl(fd, BLKGETSIZE64, &u.blksize64)) { // bytes
+ u.blksize64 /= (1024 * 1024);
+ } else {
+ xioctl(fd, BLKGETSIZE, &u.blksize32); // sectors
+ u.blksize64 = u.blksize32 / (2 * 1024);
+ }
+ if (u.blksize64 > UINT_MAX)
+ return UINT_MAX;
+ return u.blksize64;
+}
+
+static void print_timing(unsigned m, unsigned elapsed_us)
+{
+ unsigned sec = elapsed_us / 1000000;
+ unsigned hs = (elapsed_us % 1000000) / 10000;
+
+ printf("%5u MB in %u.%02u seconds = %u kB/s\n",
+ m, sec, hs,
+ /* "| 1" prevents div-by-0 */
+ (unsigned) ((unsigned long long)m * (1024 * 1000000) / (elapsed_us | 1))
+ // ~= (m * 1024) / (elapsed_us / 1000000)
+ // = kb / elapsed_sec
+ );
+}
+
+static void do_time(int cache /*,int fd*/)
+/* cache=1: time cache: repeatedly read N MB at offset 0
+ * cache=0: time device: linear read, starting at offset 0
+ */
+{
+ unsigned max_iterations, iterations;
+ unsigned start; /* doesn't need to be long long */
+ unsigned elapsed, elapsed2;
+ unsigned total_MB;
+ char *buf = xmalloc(TIMING_BUF_BYTES);
+
+ if (mlock(buf, TIMING_BUF_BYTES))
+ bb_perror_msg_and_die("mlock");
+
+ /* Clear out the device request queues & give them time to complete.
+ * NB: *small* delay. User is expected to have a clue and to not run
+ * heavy io in parallel with measurements. */
+ sync();
+ sleep(1);
+ if (cache) { /* Time cache */
+ seek_to_zero();
+ read_big_block(buf);
+ printf("Timing buffer-cache reads: ");
+ } else { /* Time device */
+ printf("Timing buffered disk reads:");
+ }
+ fflush(stdout);
+
+ /* Now do the timing */
+ iterations = 0;
+ /* Max time to run (small for cache, avoids getting
+ * huge total_MB which can overlow unsigned type) */
+ elapsed2 = 510000; /* cache */
+ max_iterations = UINT_MAX;
+ if (!cache) {
+ elapsed2 = 3000000; /* not cache */
+ /* Don't want to read past the end! */
+ max_iterations = dev_size_mb() / TIMING_BUF_MB;
+ }
+ start = monotonic_us();
+ do {
+ if (cache)
+ seek_to_zero();
+ read_big_block(buf);
+ elapsed = (unsigned)monotonic_us() - start;
+ ++iterations;
+ } while (elapsed < elapsed2 && iterations < max_iterations);
+ total_MB = iterations * TIMING_BUF_MB;
+ //printf(" elapsed:%u iterations:%u ", elapsed, iterations);
+ if (cache) {
+ /* Cache: remove lseek() and monotonic_us() overheads
+ * from elapsed */
+ start = monotonic_us();
+ do {
+ seek_to_zero();
+ elapsed2 = (unsigned)monotonic_us() - start;
+ } while (--iterations);
+ //printf(" elapsed2:%u ", elapsed2);
+ elapsed -= elapsed2;
+ total_MB *= 2; // BUFCACHE_FACTOR (why?)
+ flush_buffer_cache();
+ }
+ print_timing(total_MB, elapsed);
+ munlock(buf, TIMING_BUF_BYTES);
+ free(buf);
+}
+
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+static void bus_state_value(unsigned value)
+{
+ if (value == BUSSTATE_ON)
+ on_off(1);
+ else if (value == BUSSTATE_OFF)
+ on_off(0);
+ else if (value == BUSSTATE_TRISTATE)
+ printf(" (tristate)\n");
+ else
+ printf(" (unknown: %d)\n", value);
+}
+#endif
+
+#ifdef HDIO_DRIVE_CMD
+static void interpret_standby(uint8_t standby)
+{
+ printf(" (");
+ if (standby == 0) {
+ printf("off");
+ } else if (standby <= 240 || standby == 252 || standby == 255) {
+ /* standby is in 5 sec units */
+ printf("%u minutes %u seconds", standby / 12, (standby*5) % 60);
+ } else if (standby <= 251) {
+ unsigned t = (standby - 240); /* t is in 30 min units */;
+ printf("%u.%c hours", t / 2, (t & 1) ? '0' : '5');
+ }
+ if (standby == 253)
+ printf("vendor-specific");
+ if (standby == 254)
+ printf("reserved");
+ printf(")\n");
+}
+
+static const uint8_t xfermode_val[] ALIGN1 = {
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 64, 65, 66, 67, 68, 69, 70, 71
+};
+/* NB: we save size by _not_ storing terninating NUL! */
+static const char xfermode_name[][5] ALIGN1 = {
+ "pio0", "pio1", "pio2", "pio3", "pio4", "pio5", "pio6", "pio7",
+ "sdma0","sdma1","sdma2","sdma3","sdma4","sdma5","sdma6","sdma7",
+ "mdma0","mdma1","mdma2","mdma3","mdma4","mdma5","mdma6","mdma7",
+ "udma0","udma1","udma2","udma3","udma4","udma5","udma6","udma7"
+};
+
+static int translate_xfermode(const char *name)
+{
+ int val;
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(xfermode_val); i++) {
+ if (!strncmp(name, xfermode_name[i], 5))
+ if (strlen(name) <= 5)
+ return xfermode_val[i];
+ }
+ /* Negative numbers are invalid and are caught later */
+ val = bb_strtoi(name, NULL, 10);
+ if (!errno)
+ return val;
+ return -1;
+}
+
+static void interpret_xfermode(unsigned xfermode)
+{
+ printf(" (");
+ if (xfermode == 0)
+ printf("default PIO mode");
+ else if (xfermode == 1)
+ printf("default PIO mode, disable IORDY");
+ else if (xfermode >= 8 && xfermode <= 15)
+ printf("PIO flow control mode%u", xfermode - 8);
+ else if (xfermode >= 16 && xfermode <= 23)
+ printf("singleword DMA mode%u", xfermode - 16);
+ else if (xfermode >= 32 && xfermode <= 39)
+ printf("multiword DMA mode%u", xfermode - 32);
+ else if (xfermode >= 64 && xfermode <= 71)
+ printf("UltraDMA mode%u", xfermode - 64);
+ else
+ printf("unknown");
+ printf(")\n");
+}
+#endif /* HDIO_DRIVE_CMD */
+
+static void print_flag(int flag, const char *s, unsigned long value)
+{
+ if (flag)
+ printf(" setting %s to %ld\n", s, value);
+}
+
+static void process_dev(char *devname)
+{
+ /*int fd;*/
+ long parm, multcount;
+#ifndef HDIO_DRIVE_CMD
+ int force_operation = 0;
+#endif
+ /* Please restore args[n] to these values after each ioctl
+ except for args[2] */
+ unsigned char args[4] = { WIN_SETFEATURES, 0, 0, 0 };
+ const char *fmt = " %s\t= %2ld";
+
+ /*fd = xopen(devname, O_RDONLY | O_NONBLOCK);*/
+ xmove_fd(xopen(devname, O_RDONLY | O_NONBLOCK), fd);
+ printf("\n%s:\n", devname);
+
+ if (set_readahead) {
+ print_flag(get_readahead, "fs readahead", Xreadahead);
+ ioctl_or_warn(fd, BLKRASET, (int *)Xreadahead);
+ }
+#if ENABLE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF
+ if (unregister_hwif) {
+ printf(" attempting to unregister hwif#%lu\n", hwif);
+ ioctl_or_warn(fd, HDIO_UNREGISTER_HWIF, (int *)(unsigned long)hwif);
+ }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+ if (scan_hwif) {
+ printf(" attempting to scan hwif (0x%lx, 0x%lx, %lu)\n", hwif_data, hwif_ctrl, hwif_irq);
+ args[0] = hwif_data;
+ args[1] = hwif_ctrl;
+ args[2] = hwif_irq;
+ ioctl_or_warn(fd, HDIO_SCAN_HWIF, args);
+ args[0] = WIN_SETFEATURES;
+ args[1] = 0;
+ }
+#endif
+ if (set_piomode) {
+ if (noisy_piomode) {
+ printf(" attempting to ");
+ if (piomode == 255)
+ printf("auto-tune PIO mode\n");
+ else if (piomode < 100)
+ printf("set PIO mode to %d\n", piomode);
+ else if (piomode < 200)
+ printf("set MDMA mode to %d\n", (piomode-100));
+ else
+ printf("set UDMA mode to %d\n", (piomode-200));
+ }
+ ioctl_or_warn(fd, HDIO_SET_PIO_MODE, (int *)(unsigned long)piomode);
+ }
+ if (set_io32bit) {
+ print_flag(get_io32bit, "32-bit IO_support flag", io32bit);
+ ioctl_or_warn(fd, HDIO_SET_32BIT, (int *)io32bit);
+ }
+ if (set_mult) {
+ print_flag(get_mult, "multcount", mult);
+#ifdef HDIO_DRIVE_CMD
+ ioctl_or_warn(fd, HDIO_SET_MULTCOUNT, (void *)mult);
+#else
+ force_operation |= (!ioctl_or_warn(fd, HDIO_SET_MULTCOUNT, (void *)mult));
+#endif
+ }
+ if (set_readonly) {
+ print_flag_on_off(get_readonly, "readonly", readonly);
+ ioctl_or_warn(fd, BLKROSET, &readonly);
+ }
+ if (set_unmask) {
+ print_flag_on_off(get_unmask, "unmaskirq", unmask);
+ ioctl_or_warn(fd, HDIO_SET_UNMASKINTR, (int *)unmask);
+ }
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+ if (set_dma) {
+ print_flag_on_off(get_dma, "using_dma", dma);
+ ioctl_or_warn(fd, HDIO_SET_DMA, (int *)dma);
+ }
+#endif /* FEATURE_HDPARM_HDIO_GETSET_DMA */
+#ifdef HDIO_SET_QDMA
+ if (set_dma_q) {
+ print_flag_on_off(get_dma_q, "DMA queue_depth", dma_q);
+ ioctl_or_warn(fd, HDIO_SET_QDMA, (int *)dma_q);
+ }
+#endif
+ if (set_nowerr) {
+ print_flag_on_off(get_nowerr, "nowerr", nowerr);
+ ioctl_or_warn(fd, HDIO_SET_NOWERR, (int *)nowerr);
+ }
+ if (set_keep) {
+ print_flag_on_off(get_keep, "keep_settings", keep);
+ ioctl_or_warn(fd, HDIO_SET_KEEPSETTINGS, (int *)keep);
+ }
+#ifdef HDIO_DRIVE_CMD
+ if (set_doorlock) {
+ args[0] = doorlock ? WIN_DOORLOCK : WIN_DOORUNLOCK;
+ args[2] = 0;
+ print_flag_on_off(get_doorlock, "drive doorlock", doorlock);
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ args[0] = WIN_SETFEATURES;
+ }
+ if (set_dkeep) {
+ /* lock/unlock the drive's "feature" settings */
+ print_flag_on_off(get_dkeep, "drive keep features", dkeep);
+ args[2] = dkeep ? 0x66 : 0xcc;
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ }
+ if (set_defects) {
+ args[2] = defects ? 0x04 : 0x84;
+ print_flag(get_defects, "drive defect-mgmt", defects);
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ }
+ if (set_prefetch) {
+ args[1] = prefetch;
+ args[2] = 0xab;
+ print_flag(get_prefetch, "drive prefetch", prefetch);
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ args[1] = 0;
+ }
+ if (set_xfermode) {
+ args[1] = xfermode_requested;
+ args[2] = 3;
+ if (get_xfermode) {
+ print_flag(1, "xfermode", xfermode_requested);
+ interpret_xfermode(xfermode_requested);
+ }
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ args[1] = 0;
+ }
+ if (set_lookahead) {
+ args[2] = lookahead ? 0xaa : 0x55;
+ print_flag_on_off(get_lookahead, "drive read-lookahead", lookahead);
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ }
+ if (set_apmmode) {
+ args[2] = (apmmode == 255) ? 0x85 /* disable */ : 0x05 /* set */; /* feature register */
+ args[1] = apmmode; /* sector count register 1-255 */
+ if (get_apmmode)
+ printf(" setting APM level to %s 0x%02lX (%ld)\n", (apmmode == 255) ? "disabled" : "", apmmode, apmmode);
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ args[1] = 0;
+ }
+ if (set_wcache) {
+#ifdef DO_FLUSHCACHE
+#ifndef WIN_FLUSHCACHE
+#define WIN_FLUSHCACHE 0xe7
+#endif
+#endif /* DO_FLUSHCACHE */
+ args[2] = wcache ? 0x02 : 0x82;
+ print_flag_on_off(get_wcache, "drive write-caching", wcache);
+#ifdef DO_FLUSHCACHE
+ if (!wcache)
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &flushcache);
+#endif /* DO_FLUSHCACHE */
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+#ifdef DO_FLUSHCACHE
+ if (!wcache)
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &flushcache);
+#endif /* DO_FLUSHCACHE */
+ }
+
+ /* In code below, we do not preserve args[0], but the rest
+ is preserved, including args[2] */
+ args[2] = 0;
+
+ if (set_standbynow) {
+#ifndef WIN_STANDBYNOW1
+#define WIN_STANDBYNOW1 0xE0
+#endif
+#ifndef WIN_STANDBYNOW2
+#define WIN_STANDBYNOW2 0x94
+#endif
+ if (get_standbynow) printf(" issuing standby command\n");
+ args[0] = WIN_STANDBYNOW1;
+ ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_STANDBYNOW2);
+ }
+ if (set_sleepnow) {
+#ifndef WIN_SLEEPNOW1
+#define WIN_SLEEPNOW1 0xE6
+#endif
+#ifndef WIN_SLEEPNOW2
+#define WIN_SLEEPNOW2 0x99
+#endif
+ if (get_sleepnow) printf(" issuing sleep command\n");
+ args[0] = WIN_SLEEPNOW1;
+ ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_SLEEPNOW2);
+ }
+ if (set_seagate) {
+ args[0] = 0xfb;
+ if (get_seagate) printf(" disabling Seagate auto powersaving mode\n");
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ }
+ if (set_standby) {
+ args[0] = WIN_SETIDLE1;
+ args[1] = standby_requested;
+ if (get_standby) {
+ print_flag(1, "standby", standby_requested);
+ interpret_standby(standby_requested);
+ }
+ ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+ args[1] = 0;
+ }
+#else /* HDIO_DRIVE_CMD */
+ if (force_operation) {
+ char buf[512];
+ flush_buffer_cache();
+ if (-1 == read(fd, buf, sizeof(buf)))
+ bb_perror_msg("read(%d bytes) failed (rc=-1)", sizeof(buf));
+ }
+#endif /* HDIO_DRIVE_CMD */
+
+ if (get_mult || get_identity) {
+ multcount = -1;
+ if (ioctl(fd, HDIO_GET_MULTCOUNT, &multcount)) {
+ if (get_mult && ENABLE_IOCTL_HEX2STR_ERROR) /* To be coherent with ioctl_or_warn. */
+ bb_perror_msg("HDIO_GET_MULTCOUNT");
+ else
+ bb_perror_msg("ioctl %#x failed", HDIO_GET_MULTCOUNT);
+ } else if (get_mult) {
+ printf(fmt, "multcount", multcount);
+ on_off(multcount != 0);
+ }
+ }
+ if (get_io32bit) {
+ if (!ioctl_or_warn(fd, HDIO_GET_32BIT, &parm)) {
+ printf(" IO_support\t=%3ld (", parm);
+ if (parm == 0)
+ printf("default 16-bit)\n");
+ else if (parm == 2)
+ printf("16-bit)\n");
+ else if (parm == 1)
+ printf("32-bit)\n");
+ else if (parm == 3)
+ printf("32-bit w/sync)\n");
+ else if (parm == 8)
+ printf("Request-Queue-Bypass)\n");
+ else
+ printf("\?\?\?)\n");
+ }
+ }
+ if (get_unmask) {
+ if (!ioctl_or_warn(fd, HDIO_GET_UNMASKINTR, &parm))
+ print_value_on_off("unmaskirq", parm);
+ }
+
+
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+ if (get_dma) {
+ if (!ioctl_or_warn(fd, HDIO_GET_DMA, &parm)) {
+ printf(fmt, "using_dma", parm);
+ if (parm == 8)
+ printf(" (DMA-Assisted-PIO)\n");
+ else
+ on_off(parm != 0);
+ }
+ }
+#endif
+#ifdef HDIO_GET_QDMA
+ if (get_dma_q) {
+ if (!ioctl_or_warn(fd, HDIO_GET_QDMA, &parm))
+ print_value_on_off("queue_depth", parm);
+ }
+#endif
+ if (get_keep) {
+ if (!ioctl_or_warn(fd, HDIO_GET_KEEPSETTINGS, &parm))
+ print_value_on_off("keepsettings", parm);
+ }
+
+ if (get_nowerr) {
+ if (!ioctl_or_warn(fd, HDIO_GET_NOWERR, &parm))
+ print_value_on_off("nowerr", parm);
+ }
+ if (get_readonly) {
+ if (!ioctl_or_warn(fd, BLKROGET, &parm))
+ print_value_on_off("readonly", parm);
+ }
+ if (get_readahead) {
+ if (!ioctl_or_warn(fd, BLKRAGET, &parm))
+ print_value_on_off("readahead", parm);
+ }
+ if (get_geom) {
+ if (!ioctl_or_warn(fd, BLKGETSIZE, &parm)) {
+ struct hd_geometry g;
+
+ if (!ioctl_or_warn(fd, HDIO_GETGEO, &g))
+ printf(" geometry\t= %u/%u/%u, sectors = %ld, start = %ld\n",
+ g.cylinders, g.heads, g.sectors, parm, g.start);
+ }
+ }
+#ifdef HDIO_DRIVE_CMD
+ if (get_powermode) {
+#ifndef WIN_CHECKPOWERMODE1
+#define WIN_CHECKPOWERMODE1 0xE5
+#endif
+#ifndef WIN_CHECKPOWERMODE2
+#define WIN_CHECKPOWERMODE2 0x98
+#endif
+ const char *state;
+
+ args[0] = WIN_CHECKPOWERMODE1;
+ if (ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_CHECKPOWERMODE2)) {
+ if (errno != EIO || args[0] != 0 || args[1] != 0)
+ state = "unknown";
+ else
+ state = "sleeping";
+ } else
+ state = (args[2] == 255) ? "active/idle" : "standby";
+ args[1] = args[2] = 0;
+
+ printf(" drive state is: %s\n", state);
+ }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_DRIVE_RESET
+ if (perform_reset) {
+ ioctl_or_warn(fd, HDIO_DRIVE_RESET, NULL);
+ }
+#endif /* FEATURE_HDPARM_HDIO_DRIVE_RESET */
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+ if (perform_tristate) {
+ args[0] = 0;
+ args[1] = tristate;
+ ioctl_or_warn(fd, HDIO_TRISTATE_HWIF, &args);
+ }
+#endif /* FEATURE_HDPARM_HDIO_TRISTATE_HWIF */
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+ if (get_identity) {
+ struct hd_driveid id;
+
+ if (!ioctl(fd, HDIO_GET_IDENTITY, &id)) {
+ if (multcount != -1) {
+ id.multsect = multcount;
+ id.multsect_valid |= 1;
+ } else
+ id.multsect_valid &= ~1;
+ dump_identity(&id);
+ } else if (errno == -ENOMSG)
+ printf(" no identification info available\n");
+ else if (ENABLE_IOCTL_HEX2STR_ERROR) /* To be coherent with ioctl_or_warn */
+ bb_perror_msg("HDIO_GET_IDENTITY");
+ else
+ bb_perror_msg("ioctl %#x failed", HDIO_GET_IDENTITY);
+ }
+
+ if (get_IDentity) {
+ unsigned char args1[4+512]; /* = { ... } will eat 0.5k of rodata! */
+
+ memset(args1, 0, sizeof(args1));
+ args1[0] = WIN_IDENTIFY;
+ args1[3] = 1;
+ if (!ioctl_alt_or_warn(HDIO_DRIVE_CMD, args1, WIN_PIDENTIFY))
+ identify((void *)(args1 + 4));
+ }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+ if (set_busstate) {
+ if (get_busstate) {
+ print_flag(1, "bus state", busstate);
+ bus_state_value(busstate);
+ }
+ ioctl_or_warn(fd, HDIO_SET_BUSSTATE, (int *)(unsigned long)busstate);
+ }
+ if (get_busstate) {
+ if (!ioctl_or_warn(fd, HDIO_GET_BUSSTATE, &parm)) {
+ printf(fmt, "bus state", parm);
+ bus_state_value(parm);
+ }
+ }
+#endif
+ if (reread_partn)
+ ioctl_or_warn(fd, BLKRRPART, NULL);
+
+ if (do_ctimings)
+ do_time(1 /*,fd*/); /* time cache */
+ if (do_timings)
+ do_time(0 /*,fd*/); /* time device */
+ if (do_flush)
+ flush_buffer_cache();
+ close(fd);
+}
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static int fromhex(unsigned char c)
+{
+ if (isdigit(c))
+ return (c - '0');
+ if (c >= 'a' && c <= 'f')
+ return (c - ('a' - 10));
+ bb_error_msg_and_die("bad char: '%c' 0x%02x", c, c);
+}
+
+static void identify_from_stdin(void) NORETURN;
+static void identify_from_stdin(void)
+{
+ uint16_t sbuf[256];
+ unsigned char buf[1280];
+ unsigned char *b = (unsigned char *)buf;
+ int i;
+
+ xread(STDIN_FILENO, buf, 1280);
+
+ // Convert the newline-separated hex data into an identify block.
+
+ for (i = 0; i < 256; i++) {
+ int j;
+ for (j = 0; j < 4; j++)
+ sbuf[i] = (sbuf[i] << 4) + fromhex(*(b++));
+ }
+
+ // Parse the data.
+
+ identify(sbuf);
+}
+#else
+void identify_from_stdin(void);
+#endif
+
+/* busybox specific stuff */
+static void parse_opts(smallint *get, smallint *set, unsigned long *value, int min, int max)
+{
+ if (get) {
+ *get = 1;
+ }
+ if (optarg) {
+ *set = 1;
+ *value = xatol_range(optarg, min, max);
+ }
+}
+
+static void parse_xfermode(int flag, smallint *get, smallint *set, int *value)
+{
+ if (flag) {
+ *get = 1;
+ if (optarg) {
+ *value = translate_xfermode(optarg);
+ *set = (*value > -1);
+ }
+ }
+}
+
+/*------- getopt short options --------*/
+static const char hdparm_options[] ALIGN1 =
+ "gfu::n::p:r::m::c::k::a::B:tT"
+ USE_FEATURE_HDPARM_GET_IDENTITY("iI")
+ USE_FEATURE_HDPARM_HDIO_GETSET_DMA("d::")
+#ifdef HDIO_DRIVE_CMD
+ "S:D:P:X:K:A:L:W:CyYzZ"
+#endif
+ USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF("U:")
+#ifdef HDIO_GET_QDMA
+#ifdef HDIO_SET_QDMA
+ "Q:"
+#else
+ "Q"
+#endif
+#endif
+ USE_FEATURE_HDPARM_HDIO_DRIVE_RESET("w")
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF("x::b:")
+ USE_FEATURE_HDPARM_HDIO_SCAN_HWIF("R:");
+/*-------------------------------------*/
+
+/* our main() routine: */
+int hdparm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hdparm_main(int argc, char **argv)
+{
+ int c;
+ int flagcount = 0;
+
+ while ((c = getopt(argc, argv, hdparm_options)) >= 0) {
+ flagcount++;
+ USE_FEATURE_HDPARM_GET_IDENTITY(get_IDentity |= (c == 'I'));
+ USE_FEATURE_HDPARM_GET_IDENTITY(get_identity |= (c == 'i'));
+ get_geom |= (c == 'g');
+ do_flush |= (c == 'f');
+ if (c == 'u') parse_opts(&get_unmask, &set_unmask, &unmask, 0, 1);
+ USE_FEATURE_HDPARM_HDIO_GETSET_DMA(if (c == 'd') parse_opts(&get_dma, &set_dma, &dma, 0, 9));
+ if (c == 'n') parse_opts(&get_nowerr, &set_nowerr, &nowerr, 0, 1);
+ parse_xfermode((c == 'p'), &noisy_piomode, &set_piomode, &piomode);
+ if (c == 'r') parse_opts(&get_readonly, &set_readonly, &readonly, 0, 1);
+ if (c == 'm') parse_opts(&get_mult, &set_mult, &mult, 0, INT_MAX /*32*/);
+ if (c == 'c') parse_opts(&get_io32bit, &set_io32bit, &io32bit, 0, INT_MAX /*8*/);
+ if (c == 'k') parse_opts(&get_keep, &set_keep, &keep, 0, 1);
+ if (c == 'a') parse_opts(&get_readahead, &set_readahead, &Xreadahead, 0, INT_MAX);
+ if (c == 'B') parse_opts(&get_apmmode, &set_apmmode, &apmmode, 1, 255);
+ do_flush |= do_timings |= (c == 't');
+ do_flush |= do_ctimings |= (c == 'T');
+#ifdef HDIO_DRIVE_CMD
+ if (c == 'S') parse_opts(&get_standby, &set_standby, &standby_requested, 0, 255);
+ if (c == 'D') parse_opts(&get_defects, &set_defects, &defects, 0, INT_MAX);
+ if (c == 'P') parse_opts(&get_prefetch, &set_prefetch, &prefetch, 0, INT_MAX);
+ parse_xfermode((c == 'X'), &get_xfermode, &set_xfermode, &xfermode_requested);
+ if (c == 'K') parse_opts(&get_dkeep, &set_dkeep, &prefetch, 0, 1);
+ if (c == 'A') parse_opts(&get_lookahead, &set_lookahead, &lookahead, 0, 1);
+ if (c == 'L') parse_opts(&get_doorlock, &set_doorlock, &doorlock, 0, 1);
+ if (c == 'W') parse_opts(&get_wcache, &set_wcache, &wcache, 0, 1);
+ get_powermode |= (c == 'C');
+ get_standbynow = set_standbynow |= (c == 'y');
+ get_sleepnow = set_sleepnow |= (c == 'Y');
+ reread_partn |= (c == 'z');
+ get_seagate = set_seagate |= (c == 'Z');
+#endif
+ USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(if (c == 'U') parse_opts(NULL, &unregister_hwif, &hwif, 0, INT_MAX));
+#ifdef HDIO_GET_QDMA
+ if (c == 'Q') {
+#ifdef HDIO_SET_QDMA
+ parse_opts(&get_dma_q, &set_dma_q, &dma_q, 0, INT_MAX);
+#else
+ parse_opts(&get_dma_q, NULL, NULL, 0, 0);
+#endif
+ }
+#endif
+ USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(perform_reset = (c == 'r'));
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'x') parse_opts(NULL, &perform_tristate, &tristate, 0, 1));
+ USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'b') parse_opts(&get_busstate, &set_busstate, &busstate, 0, 2));
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+ if (c == 'R') {
+ parse_opts(NULL, &scan_hwif, &hwif_data, 0, INT_MAX);
+ hwif_ctrl = xatoi_u((argv[optind]) ? argv[optind] : "");
+ hwif_irq = xatoi_u((argv[optind+1]) ? argv[optind+1] : "");
+ /* Move past the 2 additional arguments */
+ argv += 2;
+ argc -= 2;
+ }
+#endif
+ }
+ /* When no flags are given (flagcount = 0), -acdgkmnru is assumed. */
+ if (!flagcount) {
+ get_mult = get_io32bit = get_unmask = get_keep = get_readonly = get_readahead = get_geom = 1;
+ USE_FEATURE_HDPARM_HDIO_GETSET_DMA(get_dma = 1);
+ }
+ argv += optind;
+
+ if (!*argv) {
+ if (ENABLE_FEATURE_HDPARM_GET_IDENTITY && !isatty(STDIN_FILENO))
+ identify_from_stdin(); /* EXIT */
+ bb_show_usage();
+ }
+
+ do {
+ process_dev(*argv++);
+ } while (*argv);
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/inotifyd.c b/miscutils/inotifyd.c
new file mode 100644
index 0000000..0c4b067
--- /dev/null
+++ b/miscutils/inotifyd.c
@@ -0,0 +1,152 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * simple inotify daemon
+ * reports filesystem changes via userspace agent
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * Use as follows:
+ * # inotifyd /user/space/agent dir/or/file/being/watched[:mask] ...
+ *
+ * When a filesystem event matching the specified mask is occured on specified file (or directory)
+ * a userspace agent is spawned and given the following parameters:
+ * $1. actual event(s)
+ * $2. file (or directory) name
+ * $3. name of subfile (if any), in case of watching a directory
+ *
+ * E.g. inotifyd ./dev-watcher /dev:n
+ *
+ * ./dev-watcher can be, say:
+ * #!/bin/sh
+ * echo "We have new device in here! Hello, $3!"
+ *
+ * See below for mask names explanation.
+ */
+
+#include "libbb.h"
+#include <sys/inotify.h>
+
+static const char mask_names[] ALIGN1 =
+ "a" // 0x00000001 File was accessed
+ "c" // 0x00000002 File was modified
+ "e" // 0x00000004 Metadata changed
+ "w" // 0x00000008 Writtable file was closed
+ "0" // 0x00000010 Unwrittable file closed
+ "r" // 0x00000020 File was opened
+ "m" // 0x00000040 File was moved from X
+ "y" // 0x00000080 File was moved to Y
+ "n" // 0x00000100 Subfile was created
+ "d" // 0x00000200 Subfile was deleted
+ "D" // 0x00000400 Self was deleted
+ "M" // 0x00000800 Self was moved
+;
+
+extern int inotify_init(void);
+extern int inotify_add_watch(int fd, const char *path, uint32_t mask);
+
+int inotifyd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int inotifyd_main(int argc UNUSED_PARAM, char **argv)
+{
+ int n;
+ unsigned mask = IN_ALL_EVENTS; // assume we want all events
+ struct pollfd pfd;
+ char **watched = ++argv; // watched name list
+ const char *args[] = { *argv, NULL, NULL, NULL, NULL };
+
+ // sanity check: agent and at least one watch must be given
+ if (!argv[1])
+ bb_show_usage();
+
+ // open inotify
+ pfd.fd = inotify_init();
+ if (pfd.fd < 0)
+ bb_perror_msg_and_die("no kernel support");
+
+ // setup watched
+ while (*++argv) {
+ char *path = *argv;
+ char *masks = strchr(path, ':');
+ // if mask is specified ->
+ if (masks) {
+ *masks = '\0'; // split path and mask
+ // convert mask names to mask bitset
+ mask = 0;
+ while (*++masks) {
+ int i = strchr(mask_names, *masks) - mask_names;
+ if (i >= 0) {
+ mask |= (1 << i);
+ }
+ }
+ }
+ // add watch
+ n = inotify_add_watch(pfd.fd, path, mask);
+ if (n < 0)
+ bb_perror_msg_and_die("add watch (%s) failed", path);
+ //bb_error_msg("added %d [%s]:%4X", n, path, mask);
+ }
+
+ // setup signals
+ bb_signals(BB_FATAL_SIGS, record_signo);
+
+ // do watch
+ pfd.events = POLLIN;
+ while (1) {
+ ssize_t len;
+ void *buf;
+ struct inotify_event *ie;
+
+ again:
+ if (bb_got_signal)
+ break;
+ n = poll(&pfd, 1, -1);
+ /* Signal interrupted us? */
+ if (n < 0 && errno == EINTR)
+ goto again;
+ // Under Linux, above if() is not necessary.
+ // Non-fatal signals, e.g. SIGCHLD, when set to SIG_DFL,
+ // are not interrupting poll().
+ // Thus we can just break if n <= 0 (see below),
+ // because EINTR will happen only on SIGTERM et al.
+ // But this might be not true under other Unixes,
+ // and is generally way too subtle to depend on.
+ if (n <= 0) // strange error?
+ break;
+
+ // read out all pending events
+ xioctl(pfd.fd, FIONREAD, &len);
+#define eventbuf bb_common_bufsiz1
+ ie = buf = (len <= sizeof(eventbuf)) ? eventbuf : xmalloc(len);
+ len = full_read(pfd.fd, buf, len);
+ // process events. N.B. events may vary in length
+ while (len > 0) {
+ int i;
+ char events[sizeof(mask_names)];
+ char *s = events;
+ unsigned m = ie->mask;
+
+ for (i = 0; i < sizeof(mask_names)-1; ++i, m >>= 1) {
+ if (m & 1)
+ *s++ = mask_names[i];
+ }
+ *s = '\0';
+ //bb_error_msg("exec %s %08X\t%s\t%s\t%s", agent,
+ // ie->mask, events, watched[ie->wd], ie->len ? ie->name : "");
+ args[1] = events;
+ args[2] = watched[ie->wd];
+ args[3] = ie->len ? ie->name : NULL;
+ wait4pid(xspawn((char **)args));
+ // next event
+ i = sizeof(struct inotify_event) + ie->len;
+ len -= i;
+ ie = (void*)((char*)ie + i);
+ }
+ if (eventbuf != buf)
+ free(buf);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/last.c b/miscutils/last.c
new file mode 100644
index 0000000..f8c3013
--- /dev/null
+++ b/miscutils/last.c
@@ -0,0 +1,133 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * last implementation for busybox
+ *
+ * Copyright (C) 2003-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under the GPL version 2, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <utmp.h>
+
+/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
+ * to reduce confusion */
+
+#ifndef SHUTDOWN_TIME
+# define SHUTDOWN_TIME 254
+#endif
+
+/* Grr... utmp char[] members do not have to be nul-terminated.
+ * Do what we can while still keeping this reasonably small.
+ * Note: We are assuming the ut_id[] size is fixed at 4. */
+
+#if defined UT_LINESIZE \
+ && ((UT_LINESIZE != 32) || (UT_NAMESIZE != 32) || (UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#elif defined __UT_LINESIZE \
+ && ((__UT_LINESIZE != 32) || (__UT_NAMESIZE != 64) || (__UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#endif
+
+#if EMPTY != 0 || RUN_LVL != 1 || BOOT_TIME != 2 || NEW_TIME != 3 || \
+ OLD_TIME != 4
+#error Values for the ut_type field of struct utmp changed
+#endif
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc, char **argv UNUSED_PARAM)
+{
+ struct utmp ut;
+ int n, file = STDIN_FILENO;
+ time_t t_tmp;
+ off_t pos;
+ static const char _ut_usr[] ALIGN1 =
+ "runlevel\0" "reboot\0" "shutdown\0";
+ static const char _ut_lin[] ALIGN1 =
+ "~\0" "{\0" "|\0" /* "LOGIN\0" "date\0" */;
+ enum {
+ TYPE_RUN_LVL = RUN_LVL, /* 1 */
+ TYPE_BOOT_TIME = BOOT_TIME, /* 2 */
+ TYPE_SHUTDOWN_TIME = SHUTDOWN_TIME
+ };
+ enum {
+ _TILDE = EMPTY, /* 0 */
+ TYPE_NEW_TIME, /* NEW_TIME, 3 */
+ TYPE_OLD_TIME /* OLD_TIME, 4 */
+ };
+
+ if (argc > 1) {
+ bb_show_usage();
+ }
+ file = xopen(bb_path_wtmp_file, O_RDONLY);
+
+ printf("%-10s %-14s %-18s %-12.12s %s\n",
+ "USER", "TTY", "HOST", "LOGIN", "TIME");
+ /* yikes. We reverse over the file and that is a not too elegant way */
+ pos = xlseek(file, 0, SEEK_END);
+ pos = lseek(file, pos - sizeof(ut), SEEK_SET);
+ while ((n = full_read(file, &ut, sizeof(ut))) > 0) {
+ if (n != sizeof(ut)) {
+ bb_perror_msg_and_die("short read");
+ }
+ n = index_in_strings(_ut_lin, ut.ut_line);
+ if (n == _TILDE) { /* '~' */
+#if 1
+/* do we really need to be cautious here? */
+ n = index_in_strings(_ut_usr, ut.ut_user);
+ if (++n > 0)
+ ut.ut_type = n != 3 ? n : SHUTDOWN_TIME;
+#else
+ if (strncmp(ut.ut_user, "shutdown", 8) == 0)
+ ut.ut_type = SHUTDOWN_TIME;
+ else if (strncmp(ut.ut_user, "reboot", 6) == 0)
+ ut.ut_type = BOOT_TIME;
+ else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
+ ut.ut_type = RUN_LVL;
+#endif
+ } else {
+ if (ut.ut_user[0] == '\0' || strcmp(ut.ut_user, "LOGIN") == 0) {
+ /* Don't bother. This means we can't find how long
+ * someone was logged in for. Oh well. */
+ goto next;
+ }
+ if (ut.ut_type != DEAD_PROCESS
+ && ut.ut_user[0] && ut.ut_line[0]
+ ) {
+ ut.ut_type = USER_PROCESS;
+ }
+ if (strcmp(ut.ut_user, "date") == 0) {
+ if (n == TYPE_OLD_TIME) { /* '|' */
+ ut.ut_type = OLD_TIME;
+ }
+ if (n == TYPE_NEW_TIME) { /* '{' */
+ ut.ut_type = NEW_TIME;
+ }
+ }
+ }
+
+ if (ut.ut_type != USER_PROCESS) {
+ switch (ut.ut_type) {
+ case OLD_TIME:
+ case NEW_TIME:
+ case RUN_LVL:
+ case SHUTDOWN_TIME:
+ goto next;
+ case BOOT_TIME:
+ strcpy(ut.ut_line, "system boot");
+ }
+ }
+ /* manpages say ut_tv.tv_sec *is* time_t,
+ * but some systems have it wrong */
+ t_tmp = (time_t)ut.ut_tv.tv_sec;
+ printf("%-10s %-14s %-18s %-12.12s\n",
+ ut.ut_user, ut.ut_line, ut.ut_host, ctime(&t_tmp) + 4);
+ next:
+ pos -= sizeof(ut);
+ if (pos <= 0)
+ break; /* done. */
+ xlseek(file, pos, SEEK_SET);
+ }
+
+ fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/last_fancy.c b/miscutils/last_fancy.c
new file mode 100644
index 0000000..f3ea037
--- /dev/null
+++ b/miscutils/last_fancy.c
@@ -0,0 +1,297 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * (sysvinit like) last implementation
+ *
+ * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
+ *
+ * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <utmp.h>
+
+/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
+ * to reduce confusion */
+
+#ifndef SHUTDOWN_TIME
+# define SHUTDOWN_TIME 254
+#endif
+
+#define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
+#define HEADER_LINE "USER", "TTY", \
+ INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
+#define HEADER_LINE_WIDE "USER", "TTY", \
+ INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
+
+enum {
+ NORMAL,
+ LOGGED,
+ DOWN,
+ REBOOT,
+ CRASH,
+ GONE
+};
+
+enum {
+ LAST_OPT_W = (1 << 0), /* -W wide */
+ LAST_OPT_f = (1 << 1), /* -f input file */
+ LAST_OPT_H = (1 << 2), /* -H header */
+};
+
+#define show_wide (option_mask32 & LAST_OPT_W)
+
+static void show_entry(struct utmp *ut, int state, time_t dur_secs)
+{
+ unsigned days, hours, mins;
+ char duration[32];
+ char login_time[17];
+ char logout_time[8];
+ const char *logout_str;
+ const char *duration_str;
+ time_t tmp;
+
+ /* manpages say ut_tv.tv_sec *is* time_t,
+ * but some systems have it wrong */
+ tmp = ut->ut_tv.tv_sec;
+ safe_strncpy(login_time, ctime(&tmp), 17);
+ snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
+
+ dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
+ /* unsigned int is easier to divide than time_t (which may be signed long) */
+ mins = dur_secs / 60;
+ days = mins / (24*60);
+ mins = mins % (24*60);
+ hours = mins / 60;
+ mins = mins % 60;
+
+// if (days) {
+ sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
+// } else {
+// sprintf(duration, " (%02u:%02u)", hours, mins);
+// }
+
+ logout_str = logout_time;
+ duration_str = duration;
+ switch (state) {
+ case NORMAL:
+ break;
+ case LOGGED:
+ logout_str = " still";
+ duration_str = "logged in";
+ break;
+ case DOWN:
+ logout_str = "- down ";
+ break;
+ case REBOOT:
+ break;
+ case CRASH:
+ logout_str = "- crash";
+ break;
+ case GONE:
+ logout_str = " gone";
+ duration_str = "- no logout";
+ break;
+ }
+
+ printf(HEADER_FORMAT,
+ ut->ut_user,
+ ut->ut_line,
+ show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+ show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+ ut->ut_host,
+ login_time,
+ logout_str,
+ duration_str);
+}
+
+static int get_ut_type(struct utmp *ut)
+{
+ if (ut->ut_line[0] == '~') {
+ if (strcmp(ut->ut_user, "shutdown") == 0) {
+ return SHUTDOWN_TIME;
+ }
+ if (strcmp(ut->ut_user, "reboot") == 0) {
+ return BOOT_TIME;
+ }
+ if (strcmp(ut->ut_user, "runlevel") == 0) {
+ return RUN_LVL;
+ }
+ return ut->ut_type;
+ }
+
+ if (ut->ut_user[0] == 0) {
+ return DEAD_PROCESS;
+ }
+
+ if ((ut->ut_type != DEAD_PROCESS)
+ && (strcmp(ut->ut_user, "LOGIN") != 0)
+ && ut->ut_user[0]
+ && ut->ut_line[0]
+ ) {
+ ut->ut_type = USER_PROCESS;
+ }
+
+ if (strcmp(ut->ut_user, "date") == 0) {
+ if (ut->ut_line[0] == '|') {
+ return OLD_TIME;
+ }
+ if (ut->ut_line[0] == '{') {
+ return NEW_TIME;
+ }
+ }
+ return ut->ut_type;
+}
+
+static int is_runlevel_shutdown(struct utmp *ut)
+{
+ if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc UNUSED_PARAM, char **argv)
+{
+ struct utmp ut;
+ const char *filename = _PATH_WTMP;
+ llist_t *zlist;
+ off_t pos;
+ time_t start_time;
+ time_t boot_time;
+ time_t down_time;
+ int file;
+ unsigned opt;
+ smallint going_down;
+ smallint boot_down;
+
+ opt = getopt32(argv, "Wf:" /* "H" */, &filename);
+#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
+ if (opt & LAST_OPT_H) {
+ /* Print header line */
+ if (opt & LAST_OPT_W) {
+ printf(HEADER_FORMAT, HEADER_LINE_WIDE);
+ } else {
+ printf(HEADER_FORMAT, HEADER_LINE);
+ }
+ }
+#endif
+
+ file = xopen(filename, O_RDONLY);
+ {
+ /* in case the file is empty... */
+ struct stat st;
+ fstat(file, &st);
+ start_time = st.st_ctime;
+ }
+
+ time(&down_time);
+ going_down = 0;
+ boot_down = NORMAL; /* 0 */
+ zlist = NULL;
+ boot_time = 0;
+ /* get file size, rounding down to last full record */
+ pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
+ for (;;) {
+ pos -= (off_t)sizeof(ut);
+ if (pos < 0) {
+ /* Beyond the beginning of the file boundary =>
+ * the whole file has been read. */
+ break;
+ }
+ xlseek(file, pos, SEEK_SET);
+ xread(file, &ut, sizeof(ut));
+ /* rewritten by each record, eventially will have
+ * first record's ut_tv.tv_sec: */
+ start_time = ut.ut_tv.tv_sec;
+
+ switch (get_ut_type(&ut)) {
+ case SHUTDOWN_TIME:
+ down_time = ut.ut_tv.tv_sec;
+ boot_down = DOWN;
+ going_down = 1;
+ break;
+ case RUN_LVL:
+ if (is_runlevel_shutdown(&ut)) {
+ down_time = ut.ut_tv.tv_sec;
+ going_down = 1;
+ boot_down = DOWN;
+ }
+ break;
+ case BOOT_TIME:
+ strcpy(ut.ut_line, "system boot");
+ show_entry(&ut, REBOOT, down_time);
+ boot_down = CRASH;
+ going_down = 1;
+ break;
+ case DEAD_PROCESS:
+ if (!ut.ut_line[0]) {
+ break;
+ }
+ /* add_entry */
+ llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+ break;
+ case USER_PROCESS: {
+ int show;
+
+ if (!ut.ut_line[0]) {
+ break;
+ }
+ /* find_entry */
+ show = 1;
+ {
+ llist_t *el, *next;
+ for (el = zlist; el; el = next) {
+ struct utmp *up = (struct utmp *)el->data;
+ next = el->link;
+ if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
+ if (show) {
+ show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
+ show = 0;
+ }
+ llist_unlink(&zlist, el);
+ free(el->data);
+ free(el);
+ }
+ }
+ }
+
+ if (show) {
+ int state = boot_down;
+
+ if (boot_time == 0) {
+ state = LOGGED;
+ /* Check if the process is alive */
+ if ((ut.ut_pid > 0)
+ && (kill(ut.ut_pid, 0) != 0)
+ && (errno == ESRCH)) {
+ state = GONE;
+ }
+ }
+ show_entry(&ut, state, boot_time);
+ }
+ /* add_entry */
+ llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+ break;
+ }
+ }
+
+ if (going_down) {
+ boot_time = ut.ut_tv.tv_sec;
+ llist_free(zlist, free);
+ zlist = NULL;
+ going_down = 0;
+ }
+ }
+
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ llist_free(zlist, free);
+ }
+
+ printf("\nwtmp begins %s", ctime(&start_time));
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(file);
+ fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/less.c b/miscutils/less.c
new file mode 100644
index 0000000..36d4512
--- /dev/null
+++ b/miscutils/less.c
@@ -0,0 +1,1801 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini less implementation for busybox
+ *
+ * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/*
+ * TODO:
+ * - Add more regular expression support - search modifiers, certain matches, etc.
+ * - Add more complex bracket searching - currently, nested brackets are
+ * not considered.
+ * - Add support for "F" as an input. This causes less to act in
+ * a similar way to tail -f.
+ * - Allow horizontal scrolling.
+ *
+ * Notes:
+ * - the inp file pointer is used so that keyboard input works after
+ * redirected input has been read from stdin
+ */
+
+#include <sched.h> /* sched_yield() */
+
+#include "libbb.h"
+#if ENABLE_FEATURE_LESS_REGEXP
+#include "xregex.h"
+#endif
+
+/* The escape codes for highlighted and normal text */
+#define HIGHLIGHT "\033[7m"
+#define NORMAL "\033[0m"
+/* The escape code to clear the screen */
+#define CLEAR "\033[H\033[J"
+/* The escape code to clear to end of line */
+#define CLEAR_2_EOL "\033[K"
+
+enum {
+/* Absolute max of lines eaten */
+ MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
+/* This many "after the end" lines we will show (at max) */
+ TILDES = 1,
+};
+
+/* Command line options */
+enum {
+ FLAG_E = 1 << 0,
+ FLAG_M = 1 << 1,
+ FLAG_m = 1 << 2,
+ FLAG_N = 1 << 3,
+ FLAG_TILDE = 1 << 4,
+ FLAG_I = 1 << 5,
+ FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD,
+/* hijack command line options variable for internal state vars */
+ LESS_STATE_MATCH_BACKWARDS = 1 << 15,
+};
+
+#if !ENABLE_FEATURE_LESS_REGEXP
+enum { pattern_valid = 0 };
+#endif
+
+struct globals {
+ int cur_fline; /* signed */
+ int kbd_fd; /* fd to get input from */
+ int less_gets_pos;
+/* last position in last line, taking into account tabs */
+ size_t last_line_pos;
+ unsigned max_fline;
+ unsigned max_lineno; /* this one tracks linewrap */
+ unsigned max_displayed_line;
+ unsigned width;
+#if ENABLE_FEATURE_LESS_WINCH
+ unsigned winch_counter;
+#endif
+ ssize_t eof_error; /* eof if 0, error if < 0 */
+ ssize_t readpos;
+ ssize_t readeof; /* must be signed */
+ const char **buffer;
+ const char **flines;
+ const char *empty_line_marker;
+ unsigned num_files;
+ unsigned current_file;
+ char *filename;
+ char **files;
+#if ENABLE_FEATURE_LESS_MARKS
+ unsigned num_marks;
+ unsigned mark_lines[15][2];
+#endif
+#if ENABLE_FEATURE_LESS_REGEXP
+ unsigned *match_lines;
+ int match_pos; /* signed! */
+ int wanted_match; /* signed! */
+ int num_matches;
+ regex_t pattern;
+ smallint pattern_valid;
+#endif
+ smallint terminated;
+ smalluint kbd_input_size;
+ struct termios term_orig, term_less;
+ char kbd_input[KEYCODE_BUFFER_SIZE];
+};
+#define G (*ptr_to_globals)
+#define cur_fline (G.cur_fline )
+#define kbd_fd (G.kbd_fd )
+#define less_gets_pos (G.less_gets_pos )
+#define last_line_pos (G.last_line_pos )
+#define max_fline (G.max_fline )
+#define max_lineno (G.max_lineno )
+#define max_displayed_line (G.max_displayed_line)
+#define width (G.width )
+#define winch_counter (G.winch_counter )
+/* This one is 100% not cached by compiler on read access */
+#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
+#define eof_error (G.eof_error )
+#define readpos (G.readpos )
+#define readeof (G.readeof )
+#define buffer (G.buffer )
+#define flines (G.flines )
+#define empty_line_marker (G.empty_line_marker )
+#define num_files (G.num_files )
+#define current_file (G.current_file )
+#define filename (G.filename )
+#define files (G.files )
+#define num_marks (G.num_marks )
+#define mark_lines (G.mark_lines )
+#if ENABLE_FEATURE_LESS_REGEXP
+#define match_lines (G.match_lines )
+#define match_pos (G.match_pos )
+#define num_matches (G.num_matches )
+#define wanted_match (G.wanted_match )
+#define pattern (G.pattern )
+#define pattern_valid (G.pattern_valid )
+#endif
+#define terminated (G.terminated )
+#define term_orig (G.term_orig )
+#define term_less (G.term_less )
+#define kbd_input_size (G.kbd_input_size )
+#define kbd_input (G.kbd_input )
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ less_gets_pos = -1; \
+ empty_line_marker = "~"; \
+ num_files = 1; \
+ current_file = 1; \
+ eof_error = 1; \
+ terminated = 1; \
+ USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \
+} while (0)
+
+/* flines[] are lines read from stdin, each in malloc'ed buffer.
+ * Line numbers are stored as uint32_t prepended to each line.
+ * Pointer is adjusted so that flines[i] points directly past
+ * line number. Accesor: */
+#define MEMPTR(p) ((char*)(p) - 4)
+#define LINENO(p) (*(uint32_t*)((p) - 4))
+
+
+/* Reset terminal input to normal */
+static void set_tty_cooked(void)
+{
+ fflush(stdout);
+ tcsetattr(kbd_fd, TCSANOW, &term_orig);
+}
+
+/* Move the cursor to a position (x,y), where (0,0) is the
+ top-left corner of the console */
+static void move_cursor(int line, int row)
+{
+ printf("\033[%u;%uH", line, row);
+}
+
+static void clear_line(void)
+{
+ printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
+}
+
+static void print_hilite(const char *str)
+{
+ printf(HIGHLIGHT"%s"NORMAL, str);
+}
+
+static void print_statusline(const char *str)
+{
+ clear_line();
+ printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
+}
+
+/* Exit the program gracefully */
+static void less_exit(int code)
+{
+ set_tty_cooked();
+ clear_line();
+ if (code < 0)
+ kill_myself_with_sig(- code); /* does not return */
+ exit(code);
+}
+
+#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
+ || ENABLE_FEATURE_LESS_WINCH
+static void re_wrap(void)
+{
+ int w = width;
+ int new_line_pos;
+ int src_idx;
+ int dst_idx;
+ int new_cur_fline = 0;
+ uint32_t lineno;
+ char linebuf[w + 1];
+ const char **old_flines = flines;
+ const char *s;
+ char **new_flines = NULL;
+ char *d;
+
+ if (option_mask32 & FLAG_N)
+ w -= 8;
+
+ src_idx = 0;
+ dst_idx = 0;
+ s = old_flines[0];
+ lineno = LINENO(s);
+ d = linebuf;
+ new_line_pos = 0;
+ while (1) {
+ *d = *s;
+ if (*d != '\0') {
+ new_line_pos++;
+ if (*d == '\t') /* tab */
+ new_line_pos += 7;
+ s++;
+ d++;
+ if (new_line_pos >= w) {
+ int sz;
+ /* new line is full, create next one */
+ *d = '\0';
+ next_new:
+ sz = (d - linebuf) + 1; /* + 1: NUL */
+ d = ((char*)xmalloc(sz + 4)) + 4;
+ LINENO(d) = lineno;
+ memcpy(d, linebuf, sz);
+ new_flines = xrealloc_vector(new_flines, 8, dst_idx);
+ new_flines[dst_idx] = d;
+ dst_idx++;
+ if (new_line_pos < w) {
+ /* if we came here thru "goto next_new" */
+ if (src_idx > max_fline)
+ break;
+ lineno = LINENO(s);
+ }
+ d = linebuf;
+ new_line_pos = 0;
+ }
+ continue;
+ }
+ /* *d == NUL: old line ended, go to next old one */
+ free(MEMPTR(old_flines[src_idx]));
+ /* btw, convert cur_fline... */
+ if (cur_fline == src_idx)
+ new_cur_fline = dst_idx;
+ src_idx++;
+ /* no more lines? finish last new line (and exit the loop) */
+ if (src_idx > max_fline)
+ goto next_new;
+ s = old_flines[src_idx];
+ if (lineno != LINENO(s)) {
+ /* this is not a continuation line!
+ * create next _new_ line too */
+ goto next_new;
+ }
+ }
+
+ free(old_flines);
+ flines = (const char **)new_flines;
+
+ max_fline = dst_idx - 1;
+ last_line_pos = new_line_pos;
+ cur_fline = new_cur_fline;
+ /* max_lineno is screen-size independent */
+#if ENABLE_FEATURE_LESS_REGEXP
+ pattern_valid = 0;
+#endif
+}
+#endif
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void fill_match_lines(unsigned pos);
+#else
+#define fill_match_lines(pos) ((void)0)
+#endif
+
+/* Devilishly complex routine.
+ *
+ * Has to deal with EOF and EPIPE on input,
+ * with line wrapping, with last line not ending in '\n'
+ * (possibly not ending YET!), with backspace and tabs.
+ * It reads input again if last time we got an EOF (thus supporting
+ * growing files) or EPIPE (watching output of slow process like make).
+ *
+ * Variables used:
+ * flines[] - array of lines already read. Linewrap may cause
+ * one source file line to occupy several flines[n].
+ * flines[max_fline] - last line, possibly incomplete.
+ * terminated - 1 if flines[max_fline] is 'terminated'
+ * (if there was '\n' [which isn't stored itself, we just remember
+ * that it was seen])
+ * max_lineno - last line's number, this one doesn't increment
+ * on line wrap, only on "real" new lines.
+ * readbuf[0..readeof-1] - small preliminary buffer.
+ * readbuf[readpos] - next character to add to current line.
+ * last_line_pos - screen line position of next char to be read
+ * (takes into account tabs and backspaces)
+ * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
+ */
+static void read_lines(void)
+{
+#define readbuf bb_common_bufsiz1
+ char *current_line, *p;
+ int w = width;
+ char last_terminated = terminated;
+#if ENABLE_FEATURE_LESS_REGEXP
+ unsigned old_max_fline = max_fline;
+ time_t last_time = 0;
+ unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */
+#endif
+
+ if (option_mask32 & FLAG_N)
+ w -= 8;
+
+ USE_FEATURE_LESS_REGEXP(again0:)
+
+ p = current_line = ((char*)xmalloc(w + 4)) + 4;
+ max_fline += last_terminated;
+ if (!last_terminated) {
+ const char *cp = flines[max_fline];
+ strcpy(p, cp);
+ p += strlen(current_line);
+ free(MEMPTR(flines[max_fline]));
+ /* last_line_pos is still valid from previous read_lines() */
+ } else {
+ last_line_pos = 0;
+ }
+
+ while (1) { /* read lines until we reach cur_fline or wanted_match */
+ *p = '\0';
+ terminated = 0;
+ while (1) { /* read chars until we have a line */
+ char c;
+ /* if no unprocessed chars left, eat more */
+ if (readpos >= readeof) {
+ ndelay_on(0);
+ eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+ ndelay_off(0);
+ readpos = 0;
+ readeof = eof_error;
+ if (eof_error <= 0)
+ goto reached_eof;
+ }
+ c = readbuf[readpos];
+ /* backspace? [needed for manpages] */
+ /* <tab><bs> is (a) insane and */
+ /* (b) harder to do correctly, so we refuse to do it */
+ if (c == '\x8' && last_line_pos && p[-1] != '\t') {
+ readpos++; /* eat it */
+ last_line_pos--;
+ /* was buggy (p could end up <= current_line)... */
+ *--p = '\0';
+ continue;
+ }
+ {
+ size_t new_last_line_pos = last_line_pos + 1;
+ if (c == '\t') {
+ new_last_line_pos += 7;
+ new_last_line_pos &= (~7);
+ }
+ if ((int)new_last_line_pos >= w)
+ break;
+ last_line_pos = new_last_line_pos;
+ }
+ /* ok, we will eat this char */
+ readpos++;
+ if (c == '\n') {
+ terminated = 1;
+ last_line_pos = 0;
+ break;
+ }
+ /* NUL is substituted by '\n'! */
+ if (c == '\0') c = '\n';
+ *p++ = c;
+ *p = '\0';
+ } /* end of "read chars until we have a line" loop */
+ /* Corner case: linewrap with only "" wrapping to next line */
+ /* Looks ugly on screen, so we do not store this empty line */
+ if (!last_terminated && !current_line[0]) {
+ last_terminated = 1;
+ max_lineno++;
+ continue;
+ }
+ reached_eof:
+ last_terminated = terminated;
+ flines = xrealloc_vector(flines, 8, max_fline);
+
+ flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
+ LINENO(flines[max_fline]) = max_lineno;
+ if (terminated)
+ max_lineno++;
+
+ if (max_fline >= MAXLINES) {
+ eof_error = 0; /* Pretend we saw EOF */
+ break;
+ }
+ if (!(option_mask32 & FLAG_S)
+ ? (max_fline > cur_fline + max_displayed_line)
+ : (max_fline >= cur_fline
+ && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+ ) {
+#if !ENABLE_FEATURE_LESS_REGEXP
+ break;
+#else
+ if (wanted_match >= num_matches) { /* goto_match called us */
+ fill_match_lines(old_max_fline);
+ old_max_fline = max_fline;
+ }
+ if (wanted_match < num_matches)
+ break;
+#endif
+ }
+ if (eof_error <= 0) {
+ if (eof_error < 0) {
+ if (errno == EAGAIN) {
+ /* not yet eof or error, reset flag (or else
+ * we will hog CPU - select() will return
+ * immediately */
+ eof_error = 1;
+ } else {
+ print_statusline("read error");
+ }
+ }
+#if !ENABLE_FEATURE_LESS_REGEXP
+ break;
+#else
+ if (wanted_match < num_matches) {
+ break;
+ } else { /* goto_match called us */
+ time_t t = time(NULL);
+ if (t != last_time) {
+ last_time = t;
+ if (--seconds_p1 == 0)
+ break;
+ }
+ sched_yield();
+ goto again0; /* go loop again (max 2 seconds) */
+ }
+#endif
+ }
+ max_fline++;
+ current_line = ((char*)xmalloc(w + 4)) + 4;
+ p = current_line;
+ last_line_pos = 0;
+ } /* end of "read lines until we reach cur_fline" loop */
+ fill_match_lines(old_max_fline);
+#if ENABLE_FEATURE_LESS_REGEXP
+ /* prevent us from being stuck in search for a match */
+ wanted_match = -1;
+#endif
+#undef readbuf
+}
+
+#if ENABLE_FEATURE_LESS_FLAGS
+/* Interestingly, writing calc_percent as a function saves around 32 bytes
+ * on my build. */
+static int calc_percent(void)
+{
+ unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
+ return p <= 100 ? p : 100;
+}
+
+/* Print a status line if -M was specified */
+static void m_status_print(void)
+{
+ int percentage;
+
+ if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+ return;
+
+ clear_line();
+ printf(HIGHLIGHT"%s", filename);
+ if (num_files > 1)
+ printf(" (file %i of %i)", current_file, num_files);
+ printf(" lines %i-%i/%i ",
+ cur_fline + 1, cur_fline + max_displayed_line + 1,
+ max_fline + 1);
+ if (cur_fline >= (int)(max_fline - max_displayed_line)) {
+ printf("(END)"NORMAL);
+ if (num_files > 1 && current_file != num_files)
+ printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
+ return;
+ }
+ percentage = calc_percent();
+ printf("%i%%"NORMAL, percentage);
+}
+#endif
+
+/* Print the status line */
+static void status_print(void)
+{
+ const char *p;
+
+ if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+ return;
+
+ /* Change the status if flags have been set */
+#if ENABLE_FEATURE_LESS_FLAGS
+ if (option_mask32 & (FLAG_M|FLAG_m)) {
+ m_status_print();
+ return;
+ }
+ /* No flags set */
+#endif
+
+ clear_line();
+ if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+ bb_putchar(':');
+ return;
+ }
+ p = "(END)";
+ if (!cur_fline)
+ p = filename;
+ if (num_files > 1) {
+ printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
+ p, current_file, num_files);
+ return;
+ }
+ print_hilite(p);
+}
+
+static void cap_cur_fline(int nlines)
+{
+ int diff;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ if (cur_fline + max_displayed_line > max_fline + TILDES) {
+ cur_fline -= nlines;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
+ /* As the number of lines requested was too large, we just move
+ to the end of the file */
+ if (diff > 0)
+ cur_fline += diff;
+ }
+}
+
+static const char controls[] ALIGN1 =
+ /* NUL: never encountered; TAB: not converted */
+ /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
+static const char ctrlconv[] ALIGN1 =
+ /* '\n': it's a former NUL - subst with '@', not 'J' */
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
+
+static void lineno_str(char *nbuf9, const char *line)
+{
+ nbuf9[0] = '\0';
+ if (option_mask32 & FLAG_N) {
+ const char *fmt;
+ unsigned n;
+
+ if (line == empty_line_marker) {
+ memset(nbuf9, ' ', 8);
+ nbuf9[8] = '\0';
+ return;
+ }
+ /* Width of 7 preserves tab spacing in the text */
+ fmt = "%7u ";
+ n = LINENO(line) + 1;
+ if (n > 9999999) {
+ n %= 10000000;
+ fmt = "%07u ";
+ }
+ sprintf(nbuf9, fmt, n);
+ }
+}
+
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void print_found(const char *line)
+{
+ int match_status;
+ int eflags;
+ char *growline;
+ regmatch_t match_structs;
+
+ char buf[width];
+ char nbuf9[9];
+ const char *str = line;
+ char *p = buf;
+ size_t n;
+
+ while (*str) {
+ n = strcspn(str, controls);
+ if (n) {
+ if (!str[n]) break;
+ memcpy(p, str, n);
+ p += n;
+ str += n;
+ }
+ n = strspn(str, controls);
+ memset(p, '.', n);
+ p += n;
+ str += n;
+ }
+ strcpy(p, str);
+
+ /* buf[] holds quarantined version of str */
+
+ /* Each part of the line that matches has the HIGHLIGHT
+ and NORMAL escape sequences placed around it.
+ NB: we regex against line, but insert text
+ from quarantined copy (buf[]) */
+ str = buf;
+ growline = NULL;
+ eflags = 0;
+ goto start;
+
+ while (match_status == 0) {
+ char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
+ growline ? : "",
+ match_structs.rm_so, str,
+ match_structs.rm_eo - match_structs.rm_so,
+ str + match_structs.rm_so);
+ free(growline);
+ growline = new;
+ str += match_structs.rm_eo;
+ line += match_structs.rm_eo;
+ eflags = REG_NOTBOL;
+ start:
+ /* Most of the time doesn't find the regex, optimize for that */
+ match_status = regexec(&pattern, line, 1, &match_structs, eflags);
+ /* if even "" matches, treat it as "not a match" */
+ if (match_structs.rm_so >= match_structs.rm_eo)
+ match_status = 1;
+ }
+
+ lineno_str(nbuf9, line);
+ if (!growline) {
+ printf(CLEAR_2_EOL"%s%s\n", nbuf9, str);
+ return;
+ }
+ printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str);
+ free(growline);
+}
+#else
+void print_found(const char *line);
+#endif
+
+static void print_ascii(const char *str)
+{
+ char buf[width];
+ char nbuf9[9];
+ char *p;
+ size_t n;
+
+ lineno_str(nbuf9, str);
+ printf(CLEAR_2_EOL"%s", nbuf9);
+
+ while (*str) {
+ n = strcspn(str, controls);
+ if (n) {
+ if (!str[n]) break;
+ printf("%.*s", (int) n, str);
+ str += n;
+ }
+ n = strspn(str, controls);
+ p = buf;
+ do {
+ if (*str == 0x7f)
+ *p++ = '?';
+ else if (*str == (char)0x9b)
+ /* VT100's CSI, aka Meta-ESC. Who's inventor? */
+ /* I want to know who committed this sin */
+ *p++ = '{';
+ else
+ *p++ = ctrlconv[(unsigned char)*str];
+ str++;
+ } while (--n);
+ *p = '\0';
+ print_hilite(buf);
+ }
+ puts(str);
+}
+
+/* Print the buffer */
+static void buffer_print(void)
+{
+ unsigned i;
+
+ move_cursor(0, 0);
+ for (i = 0; i <= max_displayed_line; i++)
+ if (pattern_valid)
+ print_found(buffer[i]);
+ else
+ print_ascii(buffer[i]);
+ status_print();
+}
+
+static void buffer_fill_and_print(void)
+{
+ unsigned i;
+#if ENABLE_FEATURE_LESS_DASHCMD
+ int fpos = cur_fline;
+
+ if (option_mask32 & FLAG_S) {
+ /* Go back to the beginning of this line */
+ while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
+ fpos--;
+ }
+
+ i = 0;
+ while (i <= max_displayed_line && fpos <= max_fline) {
+ int lineno = LINENO(flines[fpos]);
+ buffer[i] = flines[fpos];
+ i++;
+ do {
+ fpos++;
+ } while ((fpos <= max_fline)
+ && (option_mask32 & FLAG_S)
+ && lineno == LINENO(flines[fpos])
+ );
+ }
+#else
+ for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
+ buffer[i] = flines[cur_fline + i];
+ }
+#endif
+ for (; i <= max_displayed_line; i++) {
+ buffer[i] = empty_line_marker;
+ }
+ buffer_print();
+}
+
+/* Move the buffer up and down in the file in order to scroll */
+static void buffer_down(int nlines)
+{
+ cur_fline += nlines;
+ read_lines();
+ cap_cur_fline(nlines);
+ buffer_fill_and_print();
+}
+
+static void buffer_up(int nlines)
+{
+ cur_fline -= nlines;
+ if (cur_fline < 0) cur_fline = 0;
+ read_lines();
+ buffer_fill_and_print();
+}
+
+static void buffer_line(int linenum)
+{
+ if (linenum < 0)
+ linenum = 0;
+ cur_fline = linenum;
+ read_lines();
+ if (linenum + max_displayed_line > max_fline)
+ linenum = max_fline - max_displayed_line + TILDES;
+ if (linenum < 0)
+ linenum = 0;
+ cur_fline = linenum;
+ buffer_fill_and_print();
+}
+
+static void open_file_and_read_lines(void)
+{
+ if (filename) {
+ int fd = xopen(filename, O_RDONLY);
+ dup2(fd, 0);
+ if (fd) close(fd);
+ } else {
+ /* "less" with no arguments in argv[] */
+ /* For status line only */
+ filename = xstrdup(bb_msg_standard_input);
+ }
+ readpos = 0;
+ readeof = 0;
+ last_line_pos = 0;
+ terminated = 1;
+ read_lines();
+}
+
+/* Reinitialize everything for a new file - free the memory and start over */
+static void reinitialize(void)
+{
+ unsigned i;
+
+ if (flines) {
+ for (i = 0; i <= max_fline; i++)
+ free(MEMPTR(flines[i]));
+ free(flines);
+ flines = NULL;
+ }
+
+ max_fline = -1;
+ cur_fline = 0;
+ max_lineno = 0;
+ open_file_and_read_lines();
+ buffer_fill_and_print();
+}
+
+static ssize_t getch_nowait(void)
+{
+ int rd;
+ struct pollfd pfd[2];
+
+ pfd[0].fd = STDIN_FILENO;
+ pfd[0].events = POLLIN;
+ pfd[1].fd = kbd_fd;
+ pfd[1].events = POLLIN;
+ again:
+ tcsetattr(kbd_fd, TCSANOW, &term_less);
+ /* NB: select/poll returns whenever read will not block. Therefore:
+ * if eof is reached, select/poll will return immediately
+ * because read will immediately return 0 bytes.
+ * Even if select/poll says that input is available, read CAN block
+ * (switch fd into O_NONBLOCK'ed mode to avoid it)
+ */
+ rd = 1;
+ /* Are we interested in stdin? */
+//TODO: reuse code for determining this
+ if (!(option_mask32 & FLAG_S)
+ ? !(max_fline > cur_fline + max_displayed_line)
+ : !(max_fline >= cur_fline
+ && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+ ) {
+ if (eof_error > 0) /* did NOT reach eof yet */
+ rd = 0; /* yes, we are interested in stdin */
+ }
+ /* Position cursor if line input is done */
+ if (less_gets_pos >= 0)
+ move_cursor(max_displayed_line + 2, less_gets_pos + 1);
+ fflush(stdout);
+
+ if (kbd_input_size == 0) {
+#if ENABLE_FEATURE_LESS_WINCH
+ while (1) {
+ int r;
+ /* NB: SIGWINCH interrupts poll() */
+ r = poll(pfd + rd, 2 - rd, -1);
+ if (/*r < 0 && errno == EINTR &&*/ winch_counter)
+ return '\\'; /* anything which has no defined function */
+ if (r) break;
+ }
+#else
+ safe_poll(pfd + rd, 2 - rd, -1);
+#endif
+ }
+
+ /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
+ * would not block even if there is no input available */
+ rd = read_key(kbd_fd, &kbd_input_size, kbd_input);
+ if (rd == -1) {
+ if (errno == EAGAIN) {
+ /* No keyboard input available. Since poll() did return,
+ * we should have input on stdin */
+ read_lines();
+ buffer_fill_and_print();
+ goto again;
+ }
+ /* EOF/error (ssh session got killed etc) */
+ less_exit(0);
+ }
+ set_tty_cooked();
+ return rd;
+}
+
+/* Grab a character from input without requiring the return key. If the
+ * character is ASCII \033, get more characters and assign certain sequences
+ * special return codes. Note that this function works best with raw input. */
+static int less_getch(int pos)
+{
+ int i;
+
+ again:
+ less_gets_pos = pos;
+ i = getch_nowait();
+ less_gets_pos = -1;
+
+ /* Discard Ctrl-something chars */
+ if (i >= 0 && i < ' ' && i != 0x0d && i != 8)
+ goto again;
+ return i;
+}
+
+static char* less_gets(int sz)
+{
+ int c;
+ unsigned i = 0;
+ char *result = xzalloc(1);
+
+ while (1) {
+ c = '\0';
+ less_gets_pos = sz + i;
+ c = getch_nowait();
+ if (c == 0x0d) {
+ result[i] = '\0';
+ less_gets_pos = -1;
+ return result;
+ }
+ if (c == 0x7f)
+ c = 8;
+ if (c == 8 && i) {
+ printf("\x8 \x8");
+ i--;
+ }
+ if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
+ continue;
+ if (i >= width - sz - 1)
+ continue; /* len limit */
+ bb_putchar(c);
+ result[i++] = c;
+ result = xrealloc(result, i+1);
+ }
+}
+
+static void examine_file(void)
+{
+ char *new_fname;
+
+ print_statusline("Examine: ");
+ new_fname = less_gets(sizeof("Examine: ") - 1);
+ if (!new_fname[0]) {
+ status_print();
+ err:
+ free(new_fname);
+ return;
+ }
+ if (access(new_fname, R_OK) != 0) {
+ print_statusline("Cannot read this file");
+ goto err;
+ }
+ free(filename);
+ filename = new_fname;
+ /* files start by = argv. why we assume that argv is infinitely long??
+ files[num_files] = filename;
+ current_file = num_files + 1;
+ num_files++; */
+ files[0] = filename;
+ num_files = current_file = 1;
+ reinitialize();
+}
+
+/* This function changes the file currently being paged. direction can be one of the following:
+ * -1: go back one file
+ * 0: go to the first file
+ * 1: go forward one file */
+static void change_file(int direction)
+{
+ if (current_file != ((direction > 0) ? num_files : 1)) {
+ current_file = direction ? current_file + direction : 1;
+ free(filename);
+ filename = xstrdup(files[current_file - 1]);
+ reinitialize();
+ } else {
+ print_statusline(direction > 0 ? "No next file" : "No previous file");
+ }
+}
+
+static void remove_current_file(void)
+{
+ unsigned i;
+
+ if (num_files < 2)
+ return;
+
+ if (current_file != 1) {
+ change_file(-1);
+ for (i = 3; i <= num_files; i++)
+ files[i - 2] = files[i - 1];
+ num_files--;
+ } else {
+ change_file(1);
+ for (i = 2; i <= num_files; i++)
+ files[i - 2] = files[i - 1];
+ num_files--;
+ current_file--;
+ }
+}
+
+static void colon_process(void)
+{
+ int keypress;
+
+ /* Clear the current line and print a prompt */
+ print_statusline(" :");
+
+ keypress = less_getch(2);
+ switch (keypress) {
+ case 'd':
+ remove_current_file();
+ break;
+ case 'e':
+ examine_file();
+ break;
+#if ENABLE_FEATURE_LESS_FLAGS
+ case 'f':
+ m_status_print();
+ break;
+#endif
+ case 'n':
+ change_file(1);
+ break;
+ case 'p':
+ change_file(-1);
+ break;
+ case 'q':
+ less_exit(EXIT_SUCCESS);
+ break;
+ case 'x':
+ change_file(0);
+ break;
+ }
+}
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void normalize_match_pos(int match)
+{
+ if (match >= num_matches)
+ match = num_matches - 1;
+ if (match < 0)
+ match = 0;
+ match_pos = match;
+}
+
+static void goto_match(int match)
+{
+ if (!pattern_valid)
+ return;
+ if (match < 0)
+ match = 0;
+ /* Try to find next match if eof isn't reached yet */
+ if (match >= num_matches && eof_error > 0) {
+ wanted_match = match; /* "I want to read until I see N'th match" */
+ read_lines();
+ }
+ if (num_matches) {
+ normalize_match_pos(match);
+ buffer_line(match_lines[match_pos]);
+ } else {
+ print_statusline("No matches found");
+ }
+}
+
+static void fill_match_lines(unsigned pos)
+{
+ if (!pattern_valid)
+ return;
+ /* Run the regex on each line of the current file */
+ while (pos <= max_fline) {
+ /* If this line matches */
+ if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
+ /* and we didn't match it last time */
+ && !(num_matches && match_lines[num_matches-1] == pos)
+ ) {
+ match_lines = xrealloc_vector(match_lines, 4, num_matches);
+ match_lines[num_matches++] = pos;
+ }
+ pos++;
+ }
+}
+
+static void regex_process(void)
+{
+ char *uncomp_regex, *err;
+
+ /* Reset variables */
+ free(match_lines);
+ match_lines = NULL;
+ match_pos = 0;
+ num_matches = 0;
+ if (pattern_valid) {
+ regfree(&pattern);
+ pattern_valid = 0;
+ }
+
+ /* Get the uncompiled regular expression from the user */
+ clear_line();
+ bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
+ uncomp_regex = less_gets(1);
+ if (!uncomp_regex[0]) {
+ free(uncomp_regex);
+ buffer_print();
+ return;
+ }
+
+ /* Compile the regex and check for errors */
+ err = regcomp_or_errmsg(&pattern, uncomp_regex,
+ (option_mask32 & FLAG_I) ? REG_ICASE : 0);
+ free(uncomp_regex);
+ if (err) {
+ print_statusline(err);
+ free(err);
+ return;
+ }
+
+ pattern_valid = 1;
+ match_pos = 0;
+ fill_match_lines(0);
+ while (match_pos < num_matches) {
+ if ((int)match_lines[match_pos] > cur_fline)
+ break;
+ match_pos++;
+ }
+ if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
+ match_pos--;
+
+ /* It's possible that no matches are found yet.
+ * goto_match() will read input looking for match,
+ * if needed */
+ goto_match(match_pos);
+}
+#endif
+
+static void number_process(int first_digit)
+{
+ unsigned i;
+ int num;
+ int keypress;
+ char num_input[sizeof(int)*4]; /* more than enough */
+
+ num_input[0] = first_digit;
+
+ /* Clear the current line, print a prompt, and then print the digit */
+ clear_line();
+ printf(":%c", first_digit);
+
+ /* Receive input until a letter is given */
+ i = 1;
+ while (i < sizeof(num_input)-1) {
+ keypress = less_getch(i + 1);
+ if ((unsigned)keypress > 255 || !isdigit(num_input[i]))
+ break;
+ num_input[i] = keypress;
+ bb_putchar(keypress);
+ i++;
+ }
+
+ num_input[i] = '\0';
+ num = bb_strtou(num_input, NULL, 10);
+ /* on format error, num == -1 */
+ if (num < 1 || num > MAXLINES) {
+ buffer_print();
+ return;
+ }
+
+ /* We now know the number and the letter entered, so we process them */
+ switch (keypress) {
+ case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
+ buffer_down(num);
+ break;
+ case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
+ buffer_up(num);
+ break;
+ case 'g': case '<': case 'G': case '>':
+ cur_fline = num + max_displayed_line;
+ read_lines();
+ buffer_line(num - 1);
+ break;
+ case 'p': case '%':
+ num = num * (max_fline / 100); /* + max_fline / 2; */
+ cur_fline = num + max_displayed_line;
+ read_lines();
+ buffer_line(num);
+ break;
+#if ENABLE_FEATURE_LESS_REGEXP
+ case 'n':
+ goto_match(match_pos + num);
+ break;
+ case '/':
+ option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
+ regex_process();
+ break;
+ case '?':
+ option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
+ regex_process();
+ break;
+#endif
+ }
+}
+
+#if ENABLE_FEATURE_LESS_DASHCMD
+static void flag_change(void)
+{
+ int keypress;
+
+ clear_line();
+ bb_putchar('-');
+ keypress = less_getch(1);
+
+ switch (keypress) {
+ case 'M':
+ option_mask32 ^= FLAG_M;
+ break;
+ case 'm':
+ option_mask32 ^= FLAG_m;
+ break;
+ case 'E':
+ option_mask32 ^= FLAG_E;
+ break;
+ case '~':
+ option_mask32 ^= FLAG_TILDE;
+ break;
+ case 'S':
+ option_mask32 ^= FLAG_S;
+ buffer_fill_and_print();
+ break;
+#if ENABLE_FEATURE_LESS_LINENUMS
+ case 'N':
+ option_mask32 ^= FLAG_N;
+ re_wrap();
+ buffer_fill_and_print();
+ break;
+#endif
+ }
+}
+
+#ifdef BLOAT
+static void show_flag_status(void)
+{
+ int keypress;
+ int flag_val;
+
+ clear_line();
+ bb_putchar('_');
+ keypress = less_getch(1);
+
+ switch (keypress) {
+ case 'M':
+ flag_val = option_mask32 & FLAG_M;
+ break;
+ case 'm':
+ flag_val = option_mask32 & FLAG_m;
+ break;
+ case '~':
+ flag_val = option_mask32 & FLAG_TILDE;
+ break;
+ case 'N':
+ flag_val = option_mask32 & FLAG_N;
+ break;
+ case 'E':
+ flag_val = option_mask32 & FLAG_E;
+ break;
+ default:
+ flag_val = 0;
+ break;
+ }
+
+ clear_line();
+ printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
+}
+#endif
+
+#endif /* ENABLE_FEATURE_LESS_DASHCMD */
+
+static void save_input_to_file(void)
+{
+ const char *msg = "";
+ char *current_line;
+ unsigned i;
+ FILE *fp;
+
+ print_statusline("Log file: ");
+ current_line = less_gets(sizeof("Log file: ")-1);
+ if (current_line[0]) {
+ fp = fopen_for_write(current_line);
+ if (!fp) {
+ msg = "Error opening log file";
+ goto ret;
+ }
+ for (i = 0; i <= max_fline; i++)
+ fprintf(fp, "%s\n", flines[i]);
+ fclose(fp);
+ msg = "Done";
+ }
+ ret:
+ print_statusline(msg);
+ free(current_line);
+}
+
+#if ENABLE_FEATURE_LESS_MARKS
+static void add_mark(void)
+{
+ int letter;
+
+ print_statusline("Mark: ");
+ letter = less_getch(sizeof("Mark: ") - 1);
+
+ if (isalpha(letter)) {
+ /* If we exceed 15 marks, start overwriting previous ones */
+ if (num_marks == 14)
+ num_marks = 0;
+
+ mark_lines[num_marks][0] = letter;
+ mark_lines[num_marks][1] = cur_fline;
+ num_marks++;
+ } else {
+ print_statusline("Invalid mark letter");
+ }
+}
+
+static void goto_mark(void)
+{
+ int letter;
+ int i;
+
+ print_statusline("Go to mark: ");
+ letter = less_getch(sizeof("Go to mark: ") - 1);
+ clear_line();
+
+ if (isalpha(letter)) {
+ for (i = 0; i <= num_marks; i++)
+ if (letter == mark_lines[i][0]) {
+ buffer_line(mark_lines[i][1]);
+ break;
+ }
+ if (num_marks == 14 && letter != mark_lines[14][0])
+ print_statusline("Mark not set");
+ } else
+ print_statusline("Invalid mark letter");
+}
+#endif
+
+#if ENABLE_FEATURE_LESS_BRACKETS
+static char opp_bracket(char bracket)
+{
+ switch (bracket) {
+ case '{': case '[': /* '}' == '{' + 2. Same for '[' */
+ bracket++;
+ case '(': /* ')' == '(' + 1 */
+ bracket++;
+ break;
+ case '}': case ']':
+ bracket--;
+ case ')':
+ bracket--;
+ break;
+ };
+ return bracket;
+}
+
+static void match_right_bracket(char bracket)
+{
+ unsigned i;
+
+ if (strchr(flines[cur_fline], bracket) == NULL) {
+ print_statusline("No bracket in top line");
+ return;
+ }
+ bracket = opp_bracket(bracket);
+ for (i = cur_fline + 1; i < max_fline; i++) {
+ if (strchr(flines[i], bracket) != NULL) {
+ buffer_line(i);
+ return;
+ }
+ }
+ print_statusline("No matching bracket found");
+}
+
+static void match_left_bracket(char bracket)
+{
+ int i;
+
+ if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
+ print_statusline("No bracket in bottom line");
+ return;
+ }
+
+ bracket = opp_bracket(bracket);
+ for (i = cur_fline + max_displayed_line; i >= 0; i--) {
+ if (strchr(flines[i], bracket) != NULL) {
+ buffer_line(i);
+ return;
+ }
+ }
+ print_statusline("No matching bracket found");
+}
+#endif /* FEATURE_LESS_BRACKETS */
+
+static void keypress_process(int keypress)
+{
+ switch (keypress) {
+ case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
+ buffer_down(1);
+ break;
+ case KEYCODE_UP: case 'y': case 'k':
+ buffer_up(1);
+ break;
+ case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
+ buffer_down(max_displayed_line + 1);
+ break;
+ case KEYCODE_PAGEUP: case 'w': case 'b':
+ buffer_up(max_displayed_line + 1);
+ break;
+ case 'd':
+ buffer_down((max_displayed_line + 1) / 2);
+ break;
+ case 'u':
+ buffer_up((max_displayed_line + 1) / 2);
+ break;
+ case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
+ buffer_line(0);
+ break;
+ case KEYCODE_END: case 'G': case '>':
+ cur_fline = MAXLINES;
+ read_lines();
+ buffer_line(cur_fline);
+ break;
+ case 'q': case 'Q':
+ less_exit(EXIT_SUCCESS);
+ break;
+#if ENABLE_FEATURE_LESS_MARKS
+ case 'm':
+ add_mark();
+ buffer_print();
+ break;
+ case '\'':
+ goto_mark();
+ buffer_print();
+ break;
+#endif
+ case 'r': case 'R':
+ buffer_print();
+ break;
+ /*case 'R':
+ full_repaint();
+ break;*/
+ case 's':
+ save_input_to_file();
+ break;
+ case 'E':
+ examine_file();
+ break;
+#if ENABLE_FEATURE_LESS_FLAGS
+ case '=':
+ m_status_print();
+ break;
+#endif
+#if ENABLE_FEATURE_LESS_REGEXP
+ case '/':
+ option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
+ regex_process();
+ break;
+ case 'n':
+ goto_match(match_pos + 1);
+ break;
+ case 'N':
+ goto_match(match_pos - 1);
+ break;
+ case '?':
+ option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
+ regex_process();
+ break;
+#endif
+#if ENABLE_FEATURE_LESS_DASHCMD
+ case '-':
+ flag_change();
+ buffer_print();
+ break;
+#ifdef BLOAT
+ case '_':
+ show_flag_status();
+ break;
+#endif
+#endif
+#if ENABLE_FEATURE_LESS_BRACKETS
+ case '{': case '(': case '[':
+ match_right_bracket(keypress);
+ break;
+ case '}': case ')': case ']':
+ match_left_bracket(keypress);
+ break;
+#endif
+ case ':':
+ colon_process();
+ break;
+ }
+
+ if (isdigit(keypress))
+ number_process(keypress);
+}
+
+static void sig_catcher(int sig)
+{
+ less_exit(- sig);
+}
+
+#if ENABLE_FEATURE_LESS_WINCH
+static void sigwinch_handler(int sig UNUSED_PARAM)
+{
+ winch_counter++;
+}
+#endif
+
+int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int less_main(int argc, char **argv)
+{
+ int keypress;
+
+ INIT_G();
+
+ /* TODO: -x: do not interpret backspace, -xx: tab also */
+ /* -xxx: newline also */
+ /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
+ getopt32(argv, "EMmN~I" USE_FEATURE_LESS_DASHCMD("S"));
+ argc -= optind;
+ argv += optind;
+ num_files = argc;
+ files = argv;
+
+ /* Another popular pager, most, detects when stdout
+ * is not a tty and turns into cat. This makes sense. */
+ if (!isatty(STDOUT_FILENO))
+ return bb_cat(argv);
+
+ if (!num_files) {
+ if (isatty(STDIN_FILENO)) {
+ /* Just "less"? No args and no redirection? */
+ bb_error_msg("missing filename");
+ bb_show_usage();
+ }
+ } else {
+ filename = xstrdup(files[0]);
+ }
+
+ if (option_mask32 & FLAG_TILDE)
+ empty_line_marker = "";
+
+ kbd_fd = open(CURRENT_TTY, O_RDONLY);
+ if (kbd_fd < 0)
+ return bb_cat(argv);
+ ndelay_on(kbd_fd);
+
+ tcgetattr(kbd_fd, &term_orig);
+ term_less = term_orig;
+ term_less.c_lflag &= ~(ICANON | ECHO);
+ term_less.c_iflag &= ~(IXON | ICRNL);
+ /*term_less.c_oflag &= ~ONLCR;*/
+ term_less.c_cc[VMIN] = 1;
+ term_less.c_cc[VTIME] = 0;
+
+ get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
+ /* 20: two tabstops + 4 */
+ if (width < 20 || max_displayed_line < 3)
+ return bb_cat(argv);
+ max_displayed_line -= 2;
+
+ /* We want to restore term_orig on exit */
+ bb_signals(BB_FATAL_SIGS, sig_catcher);
+#if ENABLE_FEATURE_LESS_WINCH
+ signal(SIGWINCH, sigwinch_handler);
+#endif
+
+ buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+ reinitialize();
+ while (1) {
+#if ENABLE_FEATURE_LESS_WINCH
+ while (WINCH_COUNTER) {
+ again:
+ winch_counter--;
+ get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
+ /* 20: two tabstops + 4 */
+ if (width < 20)
+ width = 20;
+ if (max_displayed_line < 3)
+ max_displayed_line = 3;
+ max_displayed_line -= 2;
+ free(buffer);
+ buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+ /* Avoid re-wrap and/or redraw if we already know
+ * we need to do it again. These ops are expensive */
+ if (WINCH_COUNTER)
+ goto again;
+ re_wrap();
+ if (WINCH_COUNTER)
+ goto again;
+ buffer_fill_and_print();
+ /* This took some time. Loop back and check,
+ * were there another SIGWINCH? */
+ }
+#endif
+ keypress = less_getch(-1); /* -1: do not position cursor */
+ keypress_process(keypress);
+ }
+}
+
+/*
+Help text of less version 418 is below.
+If you are implementing something, keeping
+key and/or command line switch compatibility is a good idea:
+
+
+ SUMMARY OF LESS COMMANDS
+
+ Commands marked with * may be preceded by a number, N.
+ Notes in parentheses indicate the behavior if N is given.
+ h H Display this help.
+ q :q Q :Q ZZ Exit.
+ ---------------------------------------------------------------------------
+ MOVING
+ e ^E j ^N CR * Forward one line (or N lines).
+ y ^Y k ^K ^P * Backward one line (or N lines).
+ f ^F ^V SPACE * Forward one window (or N lines).
+ b ^B ESC-v * Backward one window (or N lines).
+ z * Forward one window (and set window to N).
+ w * Backward one window (and set window to N).
+ ESC-SPACE * Forward one window, but don't stop at end-of-file.
+ d ^D * Forward one half-window (and set half-window to N).
+ u ^U * Backward one half-window (and set half-window to N).
+ ESC-) RightArrow * Left one half screen width (or N positions).
+ ESC-( LeftArrow * Right one half screen width (or N positions).
+ F Forward forever; like "tail -f".
+ r ^R ^L Repaint screen.
+ R Repaint screen, discarding buffered input.
+ ---------------------------------------------------
+ Default "window" is the screen height.
+ Default "half-window" is half of the screen height.
+ ---------------------------------------------------------------------------
+ SEARCHING
+ /pattern * Search forward for (N-th) matching line.
+ ?pattern * Search backward for (N-th) matching line.
+ n * Repeat previous search (for N-th occurrence).
+ N * Repeat previous search in reverse direction.
+ ESC-n * Repeat previous search, spanning files.
+ ESC-N * Repeat previous search, reverse dir. & spanning files.
+ ESC-u Undo (toggle) search highlighting.
+ ---------------------------------------------------
+ Search patterns may be modified by one or more of:
+ ^N or ! Search for NON-matching lines.
+ ^E or * Search multiple files (pass thru END OF FILE).
+ ^F or @ Start search at FIRST file (for /) or last file (for ?).
+ ^K Highlight matches, but don't move (KEEP position).
+ ^R Don't use REGULAR EXPRESSIONS.
+ ---------------------------------------------------------------------------
+ JUMPING
+ g < ESC-< * Go to first line in file (or line N).
+ G > ESC-> * Go to last line in file (or line N).
+ p % * Go to beginning of file (or N percent into file).
+ t * Go to the (N-th) next tag.
+ T * Go to the (N-th) previous tag.
+ { ( [ * Find close bracket } ) ].
+ } ) ] * Find open bracket { ( [.
+ ESC-^F <c1> <c2> * Find close bracket <c2>.
+ ESC-^B <c1> <c2> * Find open bracket <c1>
+ ---------------------------------------------------
+ Each "find close bracket" command goes forward to the close bracket
+ matching the (N-th) open bracket in the top line.
+ Each "find open bracket" command goes backward to the open bracket
+ matching the (N-th) close bracket in the bottom line.
+ m<letter> Mark the current position with <letter>.
+ '<letter> Go to a previously marked position.
+ '' Go to the previous position.
+ ^X^X Same as '.
+ ---------------------------------------------------
+ A mark is any upper-case or lower-case letter.
+ Certain marks are predefined:
+ ^ means beginning of the file
+ $ means end of the file
+ ---------------------------------------------------------------------------
+ CHANGING FILES
+ :e [file] Examine a new file.
+ ^X^V Same as :e.
+ :n * Examine the (N-th) next file from the command line.
+ :p * Examine the (N-th) previous file from the command line.
+ :x * Examine the first (or N-th) file from the command line.
+ :d Delete the current file from the command line list.
+ = ^G :f Print current file name.
+ ---------------------------------------------------------------------------
+ MISCELLANEOUS COMMANDS
+ -<flag> Toggle a command line option [see OPTIONS below].
+ --<name> Toggle a command line option, by name.
+ _<flag> Display the setting of a command line option.
+ __<name> Display the setting of an option, by name.
+ +cmd Execute the less cmd each time a new file is examined.
+ !command Execute the shell command with $SHELL.
+ |Xcommand Pipe file between current pos & mark X to shell command.
+ v Edit the current file with $VISUAL or $EDITOR.
+ V Print version number of "less".
+ ---------------------------------------------------------------------------
+ OPTIONS
+ Most options may be changed either on the command line,
+ or from within less by using the - or -- command.
+ Options may be given in one of two forms: either a single
+ character preceded by a -, or a name preceeded by --.
+ -? ........ --help
+ Display help (from command line).
+ -a ........ --search-skip-screen
+ Forward search skips current screen.
+ -b [N] .... --buffers=[N]
+ Number of buffers.
+ -B ........ --auto-buffers
+ Don't automatically allocate buffers for pipes.
+ -c ........ --clear-screen
+ Repaint by clearing rather than scrolling.
+ -d ........ --dumb
+ Dumb terminal.
+ -D [xn.n] . --color=xn.n
+ Set screen colors. (MS-DOS only)
+ -e -E .... --quit-at-eof --QUIT-AT-EOF
+ Quit at end of file.
+ -f ........ --force
+ Force open non-regular files.
+ -F ........ --quit-if-one-screen
+ Quit if entire file fits on first screen.
+ -g ........ --hilite-search
+ Highlight only last match for searches.
+ -G ........ --HILITE-SEARCH
+ Don't highlight any matches for searches.
+ -h [N] .... --max-back-scroll=[N]
+ Backward scroll limit.
+ -i ........ --ignore-case
+ Ignore case in searches that do not contain uppercase.
+ -I ........ --IGNORE-CASE
+ Ignore case in all searches.
+ -j [N] .... --jump-target=[N]
+ Screen position of target lines.
+ -J ........ --status-column
+ Display a status column at left edge of screen.
+ -k [file] . --lesskey-file=[file]
+ Use a lesskey file.
+ -L ........ --no-lessopen
+ Ignore the LESSOPEN environment variable.
+ -m -M .... --long-prompt --LONG-PROMPT
+ Set prompt style.
+ -n -N .... --line-numbers --LINE-NUMBERS
+ Don't use line numbers.
+ -o [file] . --log-file=[file]
+ Copy to log file (standard input only).
+ -O [file] . --LOG-FILE=[file]
+ Copy to log file (unconditionally overwrite).
+ -p [pattern] --pattern=[pattern]
+ Start at pattern (from command line).
+ -P [prompt] --prompt=[prompt]
+ Define new prompt.
+ -q -Q .... --quiet --QUIET --silent --SILENT
+ Quiet the terminal bell.
+ -r -R .... --raw-control-chars --RAW-CONTROL-CHARS
+ Output "raw" control characters.
+ -s ........ --squeeze-blank-lines
+ Squeeze multiple blank lines.
+ -S ........ --chop-long-lines
+ Chop long lines.
+ -t [tag] .. --tag=[tag]
+ Find a tag.
+ -T [tagsfile] --tag-file=[tagsfile]
+ Use an alternate tags file.
+ -u -U .... --underline-special --UNDERLINE-SPECIAL
+ Change handling of backspaces.
+ -V ........ --version
+ Display the version number of "less".
+ -w ........ --hilite-unread
+ Highlight first new line after forward-screen.
+ -W ........ --HILITE-UNREAD
+ Highlight first new line after any forward movement.
+ -x [N[,...]] --tabs=[N[,...]]
+ Set tab stops.
+ -X ........ --no-init
+ Don't use termcap init/deinit strings.
+ --no-keypad
+ Don't use termcap keypad init/deinit strings.
+ -y [N] .... --max-forw-scroll=[N]
+ Forward scroll limit.
+ -z [N] .... --window=[N]
+ Set size of window.
+ -" [c[c]] . --quotes=[c[c]]
+ Set shell quote characters.
+ -~ ........ --tilde
+ Don't display tildes after end of file.
+ -# [N] .... --shift=[N]
+ Horizontal scroll amount (0 = one half screen width)
+
+ ---------------------------------------------------------------------------
+ LINE EDITING
+ These keys can be used to edit text being entered
+ on the "command line" at the bottom of the screen.
+ RightArrow ESC-l Move cursor right one character.
+ LeftArrow ESC-h Move cursor left one character.
+ CNTL-RightArrow ESC-RightArrow ESC-w Move cursor right one word.
+ CNTL-LeftArrow ESC-LeftArrow ESC-b Move cursor left one word.
+ HOME ESC-0 Move cursor to start of line.
+ END ESC-$ Move cursor to end of line.
+ BACKSPACE Delete char to left of cursor.
+ DELETE ESC-x Delete char under cursor.
+ CNTL-BACKSPACE ESC-BACKSPACE Delete word to left of cursor.
+ CNTL-DELETE ESC-DELETE ESC-X Delete word under cursor.
+ CNTL-U ESC (MS-DOS only) Delete entire line.
+ UpArrow ESC-k Retrieve previous command line.
+ DownArrow ESC-j Retrieve next command line.
+ TAB Complete filename & cycle.
+ SHIFT-TAB ESC-TAB Complete filename & reverse cycle.
+ CNTL-L Complete filename, list all.
+*/
diff --git a/miscutils/makedevs.c b/miscutils/makedevs.c
new file mode 100644
index 0000000..be08055
--- /dev/null
+++ b/miscutils/makedevs.c
@@ -0,0 +1,209 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * public domain -- Dave 'Kill a Cop' Cinege <dcinege@psychosis.com>
+ *
+ * makedevs
+ * Make ranges of device files quickly.
+ * known bugs: can't deal with alpha ranges
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_MAKEDEVS_LEAF
+/*
+makedevs NAME TYPE MAJOR MINOR FIRST LAST [s]
+TYPEs:
+b Block device
+c Character device
+f FIFO
+
+FIRST..LAST specify numbers appended to NAME.
+If 's' is the last argument, the base device is created as well.
+Examples:
+ makedevs /dev/ttyS c 4 66 2 63 -> ttyS2-ttyS63
+ makedevs /dev/hda b 3 0 0 8 s -> hda,hda1-hda8
+*/
+int makedevs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makedevs_main(int argc, char **argv)
+{
+ mode_t mode;
+ char *basedev, *type, *nodname, *buf;
+ int Smajor, Sminor, S, E;
+
+ if (argc < 7 || argv[1][0] == '-')
+ bb_show_usage();
+
+ basedev = argv[1];
+ buf = xasprintf("%s%u", argv[1], (unsigned)-1);
+ type = argv[2];
+ Smajor = xatoi_u(argv[3]);
+ Sminor = xatoi_u(argv[4]);
+ S = xatoi_u(argv[5]);
+ E = xatoi_u(argv[6]);
+ nodname = argv[7] ? basedev : buf;
+
+ mode = 0660;
+ switch (type[0]) {
+ case 'c':
+ mode |= S_IFCHR;
+ break;
+ case 'b':
+ mode |= S_IFBLK;
+ break;
+ case 'f':
+ mode |= S_IFIFO;
+ break;
+ default:
+ bb_show_usage();
+ }
+
+ while (S <= E) {
+ sprintf(buf, "%s%u", basedev, S);
+
+ /* if mode != S_IFCHR and != S_IFBLK,
+ * third param in mknod() ignored */
+ if (mknod(nodname, mode, makedev(Smajor, Sminor)))
+ bb_perror_msg("can't create %s", nodname);
+
+ /*if (nodname == basedev)*/ /* ex. /dev/hda - to /dev/hda1 ... */
+ nodname = buf;
+ S++;
+ Sminor++;
+ }
+
+ return 0;
+}
+
+#elif ENABLE_FEATURE_MAKEDEVS_TABLE
+
+/* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */
+
+int makedevs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makedevs_main(int argc UNUSED_PARAM, char **argv)
+{
+ parser_t *parser;
+ char *line = (char *)"-";
+ int ret = EXIT_SUCCESS;
+
+ opt_complementary = "=1"; /* exactly one param */
+ getopt32(argv, "d:", &line);
+ argv += optind;
+
+ xchdir(*argv); /* ensure root dir exists */
+
+ umask(0);
+
+ printf("rootdir=%s\ntable=", *argv);
+ if (NOT_LONE_DASH(line)) {
+ printf("'%s'\n", line);
+ } else {
+ puts("<stdin>");
+ }
+
+ parser = config_open(line);
+ while (config_read(parser, &line, 1, 1, "# \t", PARSE_NORMAL)) {
+ int linenum;
+ char type;
+ unsigned mode = 0755;
+ unsigned major = 0;
+ unsigned minor = 0;
+ unsigned count = 0;
+ unsigned increment = 0;
+ unsigned start = 0;
+ char name[41];
+ char user[41];
+ char group[41];
+ char *full_name = name;
+ uid_t uid;
+ gid_t gid;
+
+ linenum = parser->lineno;
+
+ if ((2 > sscanf(line, "%40s %c %o %40s %40s %u %u %u %u %u",
+ name, &type, &mode, user, group,
+ &major, &minor, &start, &increment, &count))
+ || ((unsigned)(major | minor | start | count | increment) > 255)
+ ) {
+ bb_error_msg("invalid line %d: '%s'", linenum, line);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+
+ gid = (*group) ? get_ug_id(group, xgroup2gid) : getgid();
+ uid = (*user) ? get_ug_id(user, xuname2uid) : getuid();
+ /* We are already in the right root dir,
+ * so make absolute paths relative */
+ if ('/' == *full_name)
+ full_name++;
+
+ if (type == 'd') {
+ bb_make_directory(full_name, mode | S_IFDIR, FILEUTILS_RECUR);
+ if (chown(full_name, uid, gid) == -1) {
+ chown_fail:
+ bb_perror_msg("line %d: can't chown %s", linenum, full_name);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+ if (chmod(full_name, mode) < 0) {
+ chmod_fail:
+ bb_perror_msg("line %d: can't chmod %s", linenum, full_name);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+ } else if (type == 'f') {
+ struct stat st;
+ if ((stat(full_name, &st) < 0 || !S_ISREG(st.st_mode))) {
+ bb_perror_msg("line %d: regular file '%s' does not exist", linenum, full_name);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+ if (chown(full_name, uid, gid) < 0)
+ goto chown_fail;
+ if (chmod(full_name, mode) < 0)
+ goto chmod_fail;
+ } else {
+ dev_t rdev;
+ unsigned i;
+ char *full_name_inc;
+
+ if (type == 'p') {
+ mode |= S_IFIFO;
+ } else if (type == 'c') {
+ mode |= S_IFCHR;
+ } else if (type == 'b') {
+ mode |= S_IFBLK;
+ } else {
+ bb_error_msg("line %d: unsupported file type %c", linenum, type);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+
+ full_name_inc = xmalloc(strlen(full_name) + sizeof(int)*3 + 2);
+ if (count)
+ count--;
+ for (i = start; i <= start + count; i++) {
+ sprintf(full_name_inc, count ? "%s%u" : "%s", full_name, i);
+ rdev = makedev(major, minor + (i - start) * increment);
+ if (mknod(full_name_inc, mode, rdev) < 0) {
+ bb_perror_msg("line %d: can't create node %s", linenum, full_name_inc);
+ ret = EXIT_FAILURE;
+ } else if (chown(full_name_inc, uid, gid) < 0) {
+ bb_perror_msg("line %d: can't chown %s", linenum, full_name_inc);
+ ret = EXIT_FAILURE;
+ } else if (chmod(full_name_inc, mode) < 0) {
+ bb_perror_msg("line %d: can't chmod %s", linenum, full_name_inc);
+ ret = EXIT_FAILURE;
+ }
+ }
+ free(full_name_inc);
+ }
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ config_close(parser);
+
+ return ret;
+}
+
+#else
+# error makedevs configuration error, either leaf or table must be selected
+#endif
diff --git a/miscutils/man.c b/miscutils/man.c
new file mode 100644
index 0000000..24551c0
--- /dev/null
+++ b/miscutils/man.c
@@ -0,0 +1,269 @@
+/* mini man implementation for busybox
+ * Copyright (C) 2008 Denys Vlasenko <vda.linux@googlemail.com>
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+enum {
+ OPT_a = 1, /* all */
+ OPT_w = 2, /* print path */
+};
+
+/* This is what I see on my desktop system being executed:
+
+(
+echo ".ll 12.4i"
+echo ".nr LL 12.4i"
+echo ".pl 1100i"
+gunzip -c '/usr/man/man1/bzip2.1.gz'
+echo ".\\\""
+echo ".pl \n(nlu+10"
+) | gtbl | nroff -Tlatin1 -mandoc | less
+
+*/
+
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+#define Z_SUFFIX ".lzma"
+#elif ENABLE_FEATURE_SEAMLESS_BZ2
+#define Z_SUFFIX ".bz2"
+#elif ENABLE_FEATURE_SEAMLESS_GZ
+#define Z_SUFFIX ".gz"
+#else
+#define Z_SUFFIX ""
+#endif
+
+static int show_manpage(const char *pager, char *man_filename, int man, int level);
+
+static int run_pipe(const char *pager, char *man_filename, int man, int level)
+{
+ char *cmd;
+
+ /* Prevent man page link loops */
+ if (level > 10)
+ return 0;
+
+ if (access(man_filename, R_OK) != 0)
+ return 0;
+
+ if (option_mask32 & OPT_w) {
+ puts(man_filename);
+ return 1;
+ }
+
+ if (man) { /* man page, not cat page */
+ /* Is this a link to another manpage? */
+ /* The link has the following on the first line: */
+ /* ".so another_man_page" */
+
+ struct stat sb;
+ char *line;
+ char *linkname, *p;
+
+ /* On my system:
+ * man1/genhostid.1.gz: 203 bytes - smallest real manpage
+ * man2/path_resolution.2.gz: 114 bytes - largest link
+ */
+ xstat(man_filename, &sb);
+ if (sb.st_size > 300) /* err on the safe side */
+ goto ordinary_manpage;
+
+ line = xmalloc_open_zipped_read_close(man_filename, NULL);
+ if (!line || strncmp(line, ".so ", 4) != 0) {
+ free(line);
+ goto ordinary_manpage;
+ }
+ /* Example: man2/path_resolution.2.gz contains
+ * ".so man7/path_resolution.7\n<junk>"
+ */
+ *strchrnul(line, '\n') = '\0';
+ linkname = skip_whitespace(&line[4]);
+
+ /* If link has no slashes, we just replace man page name.
+ * If link has slashes (however many), we go back *once*.
+ * ".so zzz/ggg/page.3" does NOT go back two levels. */
+ p = strrchr(man_filename, '/');
+ if (!p)
+ goto ordinary_manpage;
+ *p = '\0';
+ if (strchr(linkname, '/')) {
+ p = strrchr(man_filename, '/');
+ if (!p)
+ goto ordinary_manpage;
+ *p = '\0';
+ }
+
+ /* Links do not have .gz extensions, even if manpage
+ * is compressed */
+ man_filename = xasprintf("%s/%s" Z_SUFFIX, man_filename, linkname);
+ free(line);
+ /* Note: we leak "new" man_filename string as well... */
+ if (show_manpage(pager, man_filename, man, level + 1))
+ return 1;
+ /* else: show the link, it's better than nothing */
+ }
+
+ ordinary_manpage:
+ close(STDIN_FILENO);
+ open_zipped(man_filename); /* guaranteed to use fd 0 (STDIN_FILENO) */
+ /* "2>&1" is added so that nroff errors are shown in pager too.
+ * Otherwise it may show just empty screen */
+ cmd = xasprintf(
+ man ? "gtbl | nroff -Tlatin1 -mandoc 2>&1 | %s"
+ : "%s",
+ pager);
+ system(cmd);
+ free(cmd);
+ return 1;
+}
+
+/* man_filename is of the form "/dir/dir/dir/name.s" Z_SUFFIX */
+static int show_manpage(const char *pager, char *man_filename, int man, int level)
+{
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+ if (run_pipe(pager, man_filename, man, level))
+ return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+ strcpy(strrchr(man_filename, '.') + 1, "bz2");
+#endif
+ if (run_pipe(pager, man_filename, man, level))
+ return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_GZ
+#if ENABLE_FEATURE_SEAMLESS_LZMA || ENABLE_FEATURE_SEAMLESS_BZ2
+ strcpy(strrchr(man_filename, '.') + 1, "gz");
+#endif
+ if (run_pipe(pager, man_filename, man, level))
+ return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_LZMA || ENABLE_FEATURE_SEAMLESS_BZ2 || ENABLE_FEATURE_SEAMLESS_GZ
+ *strrchr(man_filename, '.') = '\0';
+#endif
+ if (run_pipe(pager, man_filename, man, level))
+ return 1;
+
+ return 0;
+}
+
+int man_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int man_main(int argc UNUSED_PARAM, char **argv)
+{
+ parser_t *parser;
+ const char *pager;
+ char **man_path_list;
+ char *sec_list;
+ char *cur_path, *cur_sect;
+ int count_mp, cur_mp;
+ int opt, not_found;
+ char *token[2];
+
+ opt_complementary = "-1"; /* at least one argument */
+ opt = getopt32(argv, "+aw");
+ argv += optind;
+
+ sec_list = xstrdup("1:2:3:4:5:6:7:8:9");
+ /* Last valid man_path_list[] is [0x10] */
+ count_mp = 0;
+ man_path_list = xzalloc(0x11 * sizeof(man_path_list[0]));
+ man_path_list[0] = getenv("MANPATH");
+ if (!man_path_list[0]) /* default, may be overridden by /etc/man.conf */
+ man_path_list[0] = (char*)"/usr/man";
+ else
+ count_mp++;
+ pager = getenv("MANPAGER");
+ if (!pager) {
+ pager = getenv("PAGER");
+ if (!pager)
+ pager = "more";
+ }
+
+ /* Parse man.conf */
+ parser = config_open2("/etc/man.conf", fopen_for_read);
+ while (config_read(parser, token, 2, 0, "# \t", PARSE_NORMAL)) {
+ if (!token[1])
+ continue;
+ if (strcmp("MANPATH", token[0]) == 0) {
+ /* Do we already have it? */
+ char **path_element = man_path_list;
+ while (*path_element) {
+ if (strcmp(*path_element, token[1]) == 0)
+ goto skip;
+ path_element++;
+ }
+ man_path_list = xrealloc_vector(man_path_list, 4, count_mp);
+ man_path_list[count_mp] = xstrdup(token[1]);
+ count_mp++;
+ /* man_path_list is NULL terminated */
+ /*man_path_list[count_mp] = NULL; - xrealloc_vector did it */
+ }
+ if (strcmp("MANSECT", token[0]) == 0) {
+ free(sec_list);
+ sec_list = xstrdup(token[1]);
+ }
+ skip: ;
+ }
+ config_close(parser);
+
+ not_found = 0;
+ do { /* for each argv[] */
+ int found = 0;
+ cur_mp = 0;
+
+ if (strchr(*argv, '/')) {
+ found = show_manpage(pager, *argv, /*man:*/ 1, 0);
+ goto check_found;
+ }
+ while ((cur_path = man_path_list[cur_mp++]) != NULL) {
+ /* for each MANPATH */
+ do { /* for each MANPATH item */
+ char *next_path = strchrnul(cur_path, ':');
+ int path_len = next_path - cur_path;
+ cur_sect = sec_list;
+ do { /* for each section */
+ char *next_sect = strchrnul(cur_sect, ':');
+ int sect_len = next_sect - cur_sect;
+ char *man_filename;
+ int cat0man1 = 0;
+
+ /* Search for cat, then man page */
+ while (cat0man1 < 2) {
+ int found_here;
+ man_filename = xasprintf("%.*s/%s%.*s/%s.%.*s" Z_SUFFIX,
+ path_len, cur_path,
+ "cat\0man" + (cat0man1 * 4),
+ sect_len, cur_sect,
+ *argv,
+ sect_len, cur_sect);
+ found_here = show_manpage(pager, man_filename, cat0man1, 0);
+ found |= found_here;
+ cat0man1 += found_here + 1;
+ free(man_filename);
+ }
+
+ if (found && !(opt & OPT_a))
+ goto next_arg;
+ cur_sect = next_sect;
+ while (*cur_sect == ':')
+ cur_sect++;
+ } while (*cur_sect);
+ cur_path = next_path;
+ while (*cur_path == ':')
+ cur_path++;
+ } while (*cur_path);
+ }
+ check_found:
+ if (!found) {
+ bb_error_msg("no manual entry for '%s'", *argv);
+ not_found = 1;
+ }
+ next_arg:
+ argv++;
+ } while (*argv);
+
+ return not_found;
+}
diff --git a/miscutils/microcom.c b/miscutils/microcom.c
new file mode 100644
index 0000000..a322197
--- /dev/null
+++ b/miscutils/microcom.c
@@ -0,0 +1,171 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones 'talk to modem' program - similar to 'cu -l $device'
+ * inspired by mgetty's microcom
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+// set raw tty mode
+static void xget1(int fd, struct termios *t, struct termios *oldt)
+{
+ tcgetattr(fd, oldt);
+ *t = *oldt;
+ cfmakeraw(t);
+// t->c_lflag &= ~(ISIG|ICANON|ECHO|IEXTEN);
+// t->c_iflag &= ~(BRKINT|IXON|ICRNL);
+// t->c_oflag &= ~(ONLCR);
+// t->c_cc[VMIN] = 1;
+// t->c_cc[VTIME] = 0;
+}
+
+static int xset1(int fd, struct termios *tio, const char *device)
+{
+ int ret = tcsetattr(fd, TCSAFLUSH, tio);
+
+ if (ret) {
+ bb_perror_msg("can't tcsetattr for %s", device);
+ }
+ return ret;
+}
+
+int microcom_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int microcom_main(int argc UNUSED_PARAM, char **argv)
+{
+ int sfd;
+ int nfd;
+ struct pollfd pfd[2];
+ struct termios tio0, tiosfd, tio;
+ char *device_lock_file;
+ enum {
+ OPT_X = 1 << 0, // do not respect Ctrl-X, Ctrl-@
+ OPT_s = 1 << 1, // baudrate
+ OPT_d = 1 << 2, // wait for device response, ms
+ OPT_t = 1 << 3, // timeout, ms
+ };
+ speed_t speed = 9600;
+ int delay = -1;
+ int timeout = -1;
+ unsigned opts;
+
+ // fetch options
+ opt_complementary = "=1:s+:d+:t+"; // exactly one arg, numeric options
+ opts = getopt32(argv, "Xs:d:t:", &speed, &delay, &timeout);
+// argc -= optind;
+ argv += optind;
+
+ // try to create lock file in /var/lock
+ device_lock_file = (char *)bb_basename(argv[0]);
+ device_lock_file = xasprintf("/var/lock/LCK..%s", device_lock_file);
+ sfd = open(device_lock_file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0644);
+ if (sfd < 0) {
+ // device already locked -> bail out
+ if (errno == EEXIST)
+ bb_perror_msg_and_die("can't create %s", device_lock_file);
+ // can't create lock -> don't care
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(device_lock_file);
+ device_lock_file = NULL;
+ } else {
+ // %4d to make concurrent mgetty (if any) happy.
+ // Mgetty treats 4-bytes lock files as binary,
+ // not text, PID. Making 5+ char file. Brrr...
+ fdprintf(sfd, "%4d\n", getpid());
+ close(sfd);
+ }
+
+ // setup signals
+ bb_signals(0
+ + (1 << SIGHUP)
+ + (1 << SIGINT)
+ + (1 << SIGTERM)
+ + (1 << SIGPIPE)
+ , record_signo);
+
+ // error exit code if we fail to open the device
+ bb_got_signal = 1;
+
+ // open device
+ sfd = open_or_warn(argv[0], O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (sfd < 0)
+ goto done;
+ fcntl(sfd, F_SETFL, 0);
+
+ // put device to "raw mode"
+ xget1(sfd, &tio, &tiosfd);
+ // set device speed
+ cfsetspeed(&tio, tty_value_to_baud(speed));
+ if (xset1(sfd, &tio, argv[0]))
+ goto done;
+
+ // put stdin to "raw mode" (if stdin is a TTY),
+ // handle one character at a time
+ if (isatty(STDIN_FILENO)) {
+ xget1(STDIN_FILENO, &tio, &tio0);
+ if (xset1(STDIN_FILENO, &tio, "stdin"))
+ goto done;
+ }
+
+ // main loop: check with poll(), then read/write bytes across
+ pfd[0].fd = sfd;
+ pfd[0].events = POLLIN;
+ pfd[1].fd = STDIN_FILENO;
+ pfd[1].events = POLLIN;
+
+ bb_got_signal = 0;
+ nfd = 2;
+ while (!bb_got_signal && safe_poll(pfd, nfd, timeout) > 0) {
+ if (nfd > 1 && pfd[1].revents) {
+ char c;
+ // read from stdin -> write to device
+ if (safe_read(STDIN_FILENO, &c, 1) < 1) {
+ // don't poll stdin anymore if we got EOF/error
+ nfd--;
+ goto skip_write;
+ }
+ // do we need special processing?
+ if (!(opts & OPT_X)) {
+ // ^@ sends Break
+ if (VINTR == c) {
+ tcsendbreak(sfd, 0);
+ goto skip_write;
+ }
+ // ^X exits
+ if (24 == c)
+ break;
+ }
+ write(sfd, &c, 1);
+ if (delay >= 0)
+ safe_poll(pfd, 1, delay);
+skip_write: ;
+ }
+ if (pfd[0].revents) {
+#define iobuf bb_common_bufsiz1
+ ssize_t len;
+ // read from device -> write to stdout
+ len = safe_read(sfd, iobuf, sizeof(iobuf));
+ if (len > 0)
+ full_write(STDOUT_FILENO, iobuf, len);
+ else {
+ // EOF/error -> bail out
+ bb_got_signal = SIGHUP;
+ break;
+ }
+ }
+ }
+
+ // restore device mode
+ tcsetattr(sfd, TCSAFLUSH, &tiosfd);
+
+ if (isatty(STDIN_FILENO))
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+
+done:
+ if (device_lock_file)
+ unlink(device_lock_file);
+
+ return bb_got_signal;
+}
diff --git a/miscutils/mountpoint.c b/miscutils/mountpoint.c
new file mode 100644
index 0000000..81ce429
--- /dev/null
+++ b/miscutils/mountpoint.c
@@ -0,0 +1,66 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mountpoint implementation for busybox
+ *
+ * Copyright (C) 2005 Bernhard Reutner-Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Based on sysvinit's mountpoint
+ */
+
+#include "libbb.h"
+
+int mountpoint_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mountpoint_main(int argc, char **argv)
+{
+ struct stat st;
+ char *arg;
+ int opt = getopt32(argv, "qdx");
+#define OPT_q (1)
+#define OPT_d (2)
+#define OPT_x (4)
+
+ if (optind != argc - 1)
+ bb_show_usage();
+
+ arg = argv[optind];
+
+ if ( (opt & OPT_x && stat(arg, &st) == 0) || (lstat(arg, &st) == 0) ) {
+ if (opt & OPT_x) {
+ if (S_ISBLK(st.st_mode)) {
+ printf("%u:%u\n", major(st.st_rdev),
+ minor(st.st_rdev));
+ return EXIT_SUCCESS;
+ } else {
+ if (opt & OPT_q)
+ bb_putchar('\n');
+ else
+ bb_error_msg("%s: not a block device", arg);
+ }
+ return EXIT_FAILURE;
+ } else
+ if (S_ISDIR(st.st_mode)) {
+ dev_t st_dev = st.st_dev;
+ ino_t st_ino = st.st_ino;
+ char *p = xasprintf("%s/..", arg);
+
+ if (stat(p, &st) == 0) {
+ int ret = (st_dev != st.st_dev) ||
+ (st_dev == st.st_dev && st_ino == st.st_ino);
+ if (opt & OPT_d)
+ printf("%u:%u\n", major(st_dev), minor(st_dev));
+ else if (!(opt & OPT_q))
+ printf("%s is %sa mountpoint\n", arg, ret?"":"not ");
+ return !ret;
+ }
+ } else {
+ if (!(opt & OPT_q))
+ bb_error_msg("%s: not a directory", arg);
+ return EXIT_FAILURE;
+ }
+ }
+ if (!(opt & OPT_q))
+ bb_simple_perror_msg(arg);
+ return EXIT_FAILURE;
+}
diff --git a/miscutils/mt.c b/miscutils/mt.c
new file mode 100644
index 0000000..586373d
--- /dev/null
+++ b/miscutils/mt.c
@@ -0,0 +1,140 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <sys/mtio.h>
+
+/* missing: eod/seod, stoptions, stwrthreshold, densities */
+static const short opcode_value[] = {
+ MTBSF,
+ MTBSFM,
+ MTBSR,
+ MTBSS,
+ MTCOMPRESSION,
+ MTEOM,
+ MTERASE,
+ MTFSF,
+ MTFSFM,
+ MTFSR,
+ MTFSS,
+ MTLOAD,
+ MTLOCK,
+ MTMKPART,
+ MTNOP,
+ MTOFFL,
+ MTOFFL,
+ MTRAS1,
+ MTRAS2,
+ MTRAS3,
+ MTRESET,
+ MTRETEN,
+ MTREW,
+ MTSEEK,
+ MTSETBLK,
+ MTSETDENSITY,
+ MTSETDRVBUFFER,
+ MTSETPART,
+ MTTELL,
+ MTWSM,
+ MTUNLOAD,
+ MTUNLOCK,
+ MTWEOF,
+ MTWEOF
+};
+
+static const char opcode_name[] ALIGN1 =
+ "bsf" "\0"
+ "bsfm" "\0"
+ "bsr" "\0"
+ "bss" "\0"
+ "datacompression" "\0"
+ "eom" "\0"
+ "erase" "\0"
+ "fsf" "\0"
+ "fsfm" "\0"
+ "fsr" "\0"
+ "fss" "\0"
+ "load" "\0"
+ "lock" "\0"
+ "mkpart" "\0"
+ "nop" "\0"
+ "offline" "\0"
+ "rewoffline" "\0"
+ "ras1" "\0"
+ "ras2" "\0"
+ "ras3" "\0"
+ "reset" "\0"
+ "retension" "\0"
+ "rewind" "\0"
+ "seek" "\0"
+ "setblk" "\0"
+ "setdensity" "\0"
+ "drvbuffer" "\0"
+ "setpart" "\0"
+ "tell" "\0"
+ "wset" "\0"
+ "unload" "\0"
+ "unlock" "\0"
+ "eof" "\0"
+ "weof" "\0";
+
+int mt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mt_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *file = "/dev/tape";
+ struct mtop op;
+ struct mtpos position;
+ int fd, mode, idx;
+
+ if (!argv[1]) {
+ bb_show_usage();
+ }
+
+ if (strcmp(argv[1], "-f") == 0) {
+ if (!argv[2] || !argv[3])
+ bb_show_usage();
+ file = argv[2];
+ argv += 2;
+ }
+
+ idx = index_in_strings(opcode_name, argv[1]);
+
+ if (idx < 0)
+ bb_error_msg_and_die("unrecognized opcode %s", argv[1]);
+
+ op.mt_op = opcode_value[idx];
+ if (argv[2])
+ op.mt_count = xatoi_u(argv[2]);
+ else
+ op.mt_count = 1; /* One, not zero, right? */
+
+ switch (opcode_value[idx]) {
+ case MTWEOF:
+ case MTERASE:
+ case MTWSM:
+ case MTSETDRVBUFFER:
+ mode = O_WRONLY;
+ break;
+
+ default:
+ mode = O_RDONLY;
+ break;
+ }
+
+ fd = xopen(file, mode);
+
+ switch (opcode_value[idx]) {
+ case MTTELL:
+ ioctl_or_perror_and_die(fd, MTIOCPOS, &position, "%s", file);
+ printf("At block %d\n", (int) position.mt_blkno);
+ break;
+
+ default:
+ ioctl_or_perror_and_die(fd, MTIOCTOP, &op, "%s", file);
+ break;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/raidautorun.c b/miscutils/raidautorun.c
new file mode 100644
index 0000000..a2a852b
--- /dev/null
+++ b/miscutils/raidautorun.c
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * raidautorun implementation for busybox
+ *
+ * Copyright (C) 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ */
+
+#include "libbb.h"
+
+#include <linux/major.h>
+#include <linux/raid/md_u.h>
+
+int raidautorun_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int raidautorun_main(int argc, char **argv)
+{
+ if (argc != 2)
+ bb_show_usage();
+
+ xioctl(xopen(argv[1], O_RDONLY), RAID_AUTORUN, NULL);
+
+ return EXIT_SUCCESS;
+}
diff --git a/miscutils/readahead.c b/miscutils/readahead.c
new file mode 100644
index 0000000..fb71ce8
--- /dev/null
+++ b/miscutils/readahead.c
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * readahead implementation for busybox
+ *
+ * Preloads the given files in RAM, to reduce access time.
+ * Does this by calling the readahead(2) system call.
+ *
+ * Copyright (C) 2006 Michael Opdenacker <michael@free-electrons.com>
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int readahead_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int readahead_main(int argc, char **argv)
+{
+ int retval = EXIT_SUCCESS;
+
+ if (argc == 1) bb_show_usage();
+
+ while (*++argv) {
+ int fd = open_or_warn(*argv, O_RDONLY);
+ if (fd >= 0) {
+ off_t len;
+ int r;
+
+ /* fdlength was reported to be unreliable - use seek */
+ len = xlseek(fd, 0, SEEK_END);
+ xlseek(fd, 0, SEEK_SET);
+ r = readahead(fd, 0, len);
+ close(fd);
+ if (r >= 0)
+ continue;
+ }
+ retval = EXIT_FAILURE;
+ }
+
+ return retval;
+}
diff --git a/miscutils/runlevel.c b/miscutils/runlevel.c
new file mode 100644
index 0000000..6e10d9c
--- /dev/null
+++ b/miscutils/runlevel.c
@@ -0,0 +1,43 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * runlevel Prints out the previous and the current runlevel.
+ *
+ * Version: @(#)runlevel 1.20 16-Apr-1997 MvS
+ *
+ * This file is part of the sysvinit suite,
+ * Copyright 1991-1997 Miquel van Smoorenburg.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * initially busyboxified by Bernhard Reutner-Fischer
+ */
+
+#include <utmp.h>
+#include "libbb.h"
+
+int runlevel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runlevel_main(int argc, char **argv)
+{
+ struct utmp *ut;
+ char prev;
+
+ if (argc > 1) utmpname(argv[1]);
+
+ setutent();
+ while ((ut = getutent()) != NULL) {
+ if (ut->ut_type == RUN_LVL) {
+ prev = ut->ut_pid / 256;
+ if (prev == 0) prev = 'N';
+ printf("%c %c\n", prev, ut->ut_pid % 256);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ endutent();
+ return 0;
+ }
+ }
+
+ puts("unknown");
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ endutent();
+ return 1;
+}
diff --git a/miscutils/rx.c b/miscutils/rx.c
new file mode 100644
index 0000000..94eb452
--- /dev/null
+++ b/miscutils/rx.c
@@ -0,0 +1,254 @@
+/* vi: set sw=4 ts=4: */
+/*-------------------------------------------------------------------------
+ * Filename: xmodem.c
+ * Copyright: Copyright (C) 2001, Hewlett-Packard Company
+ * Author: Christopher Hoover <ch@hpl.hp.com>
+ * Description: xmodem functionality for uploading of kernels
+ * and the like
+ * Created at: Thu Dec 20 01:58:08 PST 2001
+ *-----------------------------------------------------------------------*/
+/*
+ * xmodem.c: xmodem functionality for uploading of kernels and
+ * the like
+ *
+ * Copyright (C) 2001 Hewlett-Packard Laboratories
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * This was originally written for blob and then adapted for busybox.
+ */
+
+#include "libbb.h"
+
+#define SOH 0x01
+#define STX 0x02
+#define EOT 0x04
+#define ACK 0x06
+#define NAK 0x15
+#define BS 0x08
+
+/*
+Cf:
+ http://www.textfiles.com/apple/xmodem
+ http://www.phys.washington.edu/~belonis/xmodem/docxmodem.txt
+ http://www.phys.washington.edu/~belonis/xmodem/docymodem.txt
+ http://www.phys.washington.edu/~belonis/xmodem/modmprot.col
+*/
+
+#define TIMEOUT 1
+#define TIMEOUT_LONG 10
+#define MAXERRORS 10
+
+#define read_fd STDIN_FILENO
+#define write_fd STDOUT_FILENO
+
+static int read_byte(unsigned timeout)
+{
+ char buf[1];
+ int n;
+
+ alarm(timeout);
+ /* NOT safe_read! We want ALRM to interrupt us */
+ n = read(read_fd, buf, 1);
+ alarm(0);
+ if (n == 1)
+ return (unsigned char)buf[0];
+ return -1;
+}
+
+static int receive(/*int read_fd, */int file_fd)
+{
+ unsigned char blockBuf[1024];
+ unsigned errors = 0;
+ unsigned wantBlockNo = 1;
+ unsigned length = 0;
+ int do_crc = 1;
+ char nak = 'C';
+ unsigned timeout = TIMEOUT_LONG;
+
+ /* Flush pending input */
+ tcflush(read_fd, TCIFLUSH);
+
+ /* Ask for CRC; if we get errors, we will go with checksum */
+ full_write(write_fd, &nak, 1);
+
+ for (;;) {
+ int blockBegin;
+ int blockNo, blockNoOnesCompl;
+ int blockLength;
+ int cksum_crc; /* cksum OR crc */
+ int expected;
+ int i,j;
+
+ blockBegin = read_byte(timeout);
+ if (blockBegin < 0)
+ goto timeout;
+
+ timeout = TIMEOUT;
+ nak = NAK;
+
+ switch (blockBegin) {
+ case SOH:
+ case STX:
+ break;
+
+ case EOT:
+ nak = ACK;
+ full_write(write_fd, &nak, 1);
+ return length;
+
+ default:
+ goto error;
+ }
+
+ /* block no */
+ blockNo = read_byte(TIMEOUT);
+ if (blockNo < 0)
+ goto timeout;
+
+ /* block no one's compliment */
+ blockNoOnesCompl = read_byte(TIMEOUT);
+ if (blockNoOnesCompl < 0)
+ goto timeout;
+
+ if (blockNo != (255 - blockNoOnesCompl)) {
+ bb_error_msg("bad block ones compl");
+ goto error;
+ }
+
+ blockLength = (blockBegin == SOH) ? 128 : 1024;
+
+ for (i = 0; i < blockLength; i++) {
+ int cc = read_byte(TIMEOUT);
+ if (cc < 0)
+ goto timeout;
+ blockBuf[i] = cc;
+ }
+
+ if (do_crc) {
+ cksum_crc = read_byte(TIMEOUT);
+ if (cksum_crc < 0)
+ goto timeout;
+ cksum_crc = (cksum_crc << 8) | read_byte(TIMEOUT);
+ if (cksum_crc < 0)
+ goto timeout;
+ } else {
+ cksum_crc = read_byte(TIMEOUT);
+ if (cksum_crc < 0)
+ goto timeout;
+ }
+
+ if (blockNo == ((wantBlockNo - 1) & 0xff)) {
+ /* a repeat of the last block is ok, just ignore it. */
+ /* this also ignores the initial block 0 which is */
+ /* meta data. */
+ goto next;
+ }
+ if (blockNo != (wantBlockNo & 0xff)) {
+ bb_error_msg("unexpected block no, 0x%08x, expecting 0x%08x", blockNo, wantBlockNo);
+ goto error;
+ }
+
+ expected = 0;
+ if (do_crc) {
+ for (i = 0; i < blockLength; i++) {
+ expected = expected ^ blockBuf[i] << 8;
+ for (j = 0; j < 8; j++) {
+ if (expected & 0x8000)
+ expected = expected << 1 ^ 0x1021;
+ else
+ expected = expected << 1;
+ }
+ }
+ expected &= 0xffff;
+ } else {
+ for (i = 0; i < blockLength; i++)
+ expected += blockBuf[i];
+ expected &= 0xff;
+ }
+ if (cksum_crc != expected) {
+ bb_error_msg(do_crc ? "crc error, expected 0x%04x, got 0x%04x"
+ : "checksum error, expected 0x%02x, got 0x%02x",
+ expected, cksum_crc);
+ goto error;
+ }
+
+ wantBlockNo++;
+ length += blockLength;
+
+ errno = 0;
+ if (full_write(file_fd, blockBuf, blockLength) != blockLength) {
+ bb_perror_msg("can't write to file");
+ goto fatal;
+ }
+ next:
+ errors = 0;
+ nak = ACK;
+ full_write(write_fd, &nak, 1);
+ continue;
+ error:
+ timeout:
+ errors++;
+ if (errors == MAXERRORS) {
+ /* Abort */
+
+ /* if were asking for crc, try again w/o crc */
+ if (nak == 'C') {
+ nak = NAK;
+ errors = 0;
+ do_crc = 0;
+ goto timeout;
+ }
+ bb_error_msg("too many errors; giving up");
+ fatal:
+ /* 5 CAN followed by 5 BS. Don't try too hard... */
+ safe_write(write_fd, "\030\030\030\030\030\010\010\010\010\010", 10);
+ return -1;
+ }
+
+ /* Flush pending input */
+ tcflush(read_fd, TCIFLUSH);
+
+ full_write(write_fd, &nak, 1);
+ } /* for (;;) */
+}
+
+static void sigalrm_handler(int UNUSED_PARAM signum)
+{
+}
+
+int rx_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rx_main(int argc, char **argv)
+{
+ struct termios tty, orig_tty;
+ int termios_err;
+ int file_fd;
+ int n;
+
+ if (argc != 2)
+ bb_show_usage();
+
+ /* Disabled by vda:
+ * why we can't receive from stdin? Why we *require*
+ * controlling tty?? */
+ /*read_fd = xopen(CURRENT_TTY, O_RDWR);*/
+ file_fd = xopen(argv[1], O_RDWR|O_CREAT|O_TRUNC);
+
+ termios_err = tcgetattr(read_fd, &tty);
+ if (termios_err == 0) {
+ orig_tty = tty;
+ cfmakeraw(&tty);
+ tcsetattr(read_fd, TCSAFLUSH, &tty);
+ }
+
+ /* No SA_RESTART: we want ALRM to interrupt read() */
+ signal_no_SA_RESTART_empty_mask(SIGALRM, sigalrm_handler);
+
+ n = receive(file_fd);
+
+ if (termios_err == 0)
+ tcsetattr(read_fd, TCSAFLUSH, &orig_tty);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(file_fd);
+ fflush_stdout_and_exit(n >= 0);
+}
diff --git a/miscutils/setsid.c b/miscutils/setsid.c
new file mode 100644
index 0000000..127adf6
--- /dev/null
+++ b/miscutils/setsid.c
@@ -0,0 +1,35 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setsid.c -- execute a command in a new session
+ * Rick Sladkey <jrs@world.std.com>
+ * In the public domain.
+ *
+ * 1999-02-22 Arkadiusz Mickiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2001-01-18 John Fremlin <vii@penguinpowered.com>
+ * - fork in case we are process group leader
+ *
+ * 2004-11-12 Paul Fox
+ * - busyboxed
+ */
+
+#include "libbb.h"
+
+int setsid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setsid_main(int argc UNUSED_PARAM, char **argv)
+{
+ if (!argv[1])
+ bb_show_usage();
+
+ /* setsid() is allowed only when we are not a process group leader.
+ * Otherwise our PID serves as PGID of some existing process group
+ * and cannot be used as PGID of a new process group. */
+ if (getpgrp() == getpid())
+ forkexit_or_rexec(argv);
+
+ setsid(); /* no error possible */
+
+ BB_EXECVP(argv[1], argv + 1);
+ bb_simple_perror_msg_and_die(argv[1]);
+}
diff --git a/miscutils/strings.c b/miscutils/strings.c
new file mode 100644
index 0000000..3e02b53
--- /dev/null
+++ b/miscutils/strings.c
@@ -0,0 +1,84 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * strings implementation for busybox
+ *
+ * Copyright Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#define WHOLE_FILE 1
+#define PRINT_NAME 2
+#define PRINT_OFFSET 4
+#define SIZE 8
+
+int strings_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int strings_main(int argc UNUSED_PARAM, char **argv)
+{
+ int n, c, status = EXIT_SUCCESS;
+ unsigned opt;
+ unsigned count;
+ off_t offset;
+ FILE *file;
+ char *string;
+ const char *fmt = "%s: ";
+ const char *n_arg = "4";
+
+ opt = getopt32(argv, "afon:", &n_arg);
+ /* -a is our default behaviour */
+ /*argc -= optind;*/
+ argv += optind;
+
+ n = xatou_range(n_arg, 1, INT_MAX);
+ string = xzalloc(n + 1);
+ n--;
+
+ if (!*argv) {
+ fmt = "{%s}: ";
+ *--argv = (char *)bb_msg_standard_input;
+ }
+
+ do {
+ file = fopen_or_warn_stdin(*argv);
+ if (!file) {
+ status = EXIT_FAILURE;
+ continue;
+ }
+ offset = 0;
+ count = 0;
+ do {
+ c = fgetc(file);
+ if (isprint(c) || c == '\t') {
+ if (count > n) {
+ bb_putchar(c);
+ } else {
+ string[count] = c;
+ if (count == n) {
+ if (opt & PRINT_NAME) {
+ printf(fmt, *argv);
+ }
+ if (opt & PRINT_OFFSET) {
+ printf("%7"OFF_FMT"o ", offset - n);
+ }
+ fputs(string, stdout);
+ }
+ count++;
+ }
+ } else {
+ if (count > n) {
+ bb_putchar('\n');
+ }
+ count = 0;
+ }
+ offset++;
+ } while (c != EOF);
+ fclose_if_not_stdin(file);
+ } while (*++argv);
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(string);
+
+ fflush_stdout_and_exit(status);
+}
diff --git a/miscutils/taskset.c b/miscutils/taskset.c
new file mode 100644
index 0000000..a0bbf0a
--- /dev/null
+++ b/miscutils/taskset.c
@@ -0,0 +1,137 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * taskset - retrieve or set a processes' CPU affinity
+ * Copyright (c) 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include "libbb.h"
+
+#if ENABLE_FEATURE_TASKSET_FANCY
+#define TASKSET_PRINTF_MASK "%s"
+/* craft a string from the mask */
+static char *from_cpuset(cpu_set_t *mask)
+{
+ int i;
+ char *ret = NULL;
+ char *str = xzalloc((CPU_SETSIZE / 4) + 1); /* we will leak it */
+
+ for (i = CPU_SETSIZE - 4; i >= 0; i -= 4) {
+ int val = 0;
+ int off;
+ for (off = 0; off <= 3; ++off)
+ if (CPU_ISSET(i + off, mask))
+ val |= 1 << off;
+ if (!ret && val)
+ ret = str;
+ *str++ = bb_hexdigits_upcase[val] | 0x20;
+ }
+ return ret;
+}
+#else
+#define TASKSET_PRINTF_MASK "%llx"
+static unsigned long long from_cpuset(cpu_set_t *mask)
+{
+ struct BUG_CPU_SETSIZE_is_too_small {
+ char BUG_CPU_SETSIZE_is_too_small[
+ CPU_SETSIZE < sizeof(int) ? -1 : 1];
+ };
+ char *p = (void*)mask;
+
+ /* Take the least significant bits. Careful!
+ * Consider both CPU_SETSIZE=4 and CPU_SETSIZE=1024 cases
+ */
+#if BB_BIG_ENDIAN
+ /* For big endian, it means LAST bits */
+ if (CPU_SETSIZE < sizeof(long))
+ p += CPU_SETSIZE - sizeof(int);
+ else if (CPU_SETSIZE < sizeof(long long))
+ p += CPU_SETSIZE - sizeof(long);
+ else
+ p += CPU_SETSIZE - sizeof(long long);
+#endif
+ if (CPU_SETSIZE < sizeof(long))
+ return *(unsigned*)p;
+ if (CPU_SETSIZE < sizeof(long long))
+ return *(unsigned long*)p;
+ return *(unsigned long long*)p;
+}
+#endif
+
+
+int taskset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int taskset_main(int argc UNUSED_PARAM, char **argv)
+{
+ cpu_set_t mask;
+ pid_t pid = 0;
+ unsigned opt_p;
+ const char *current_new;
+ char *pid_str;
+ char *aff = aff; /* for compiler */
+
+ /* 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:..." */
+
+ opt_complementary = "-1"; /* at least 1 arg */
+ opt_p = getopt32(argv, "+p");
+ argv += optind;
+
+ if (opt_p) {
+ pid_str = *argv++;
+ if (*argv) { /* "-p <aff> <pid> ...rest.is.ignored..." */
+ aff = pid_str;
+ 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 = *argv++; /* <aff> <cmd...> */
+ if (!*argv)
+ bb_show_usage();
+ }
+
+ current_new = "current\0new";
+ if (opt_p) {
+ print_aff:
+ if (sched_getaffinity(pid, sizeof(mask), &mask) < 0)
+ bb_perror_msg_and_die("can't %cet pid %d's affinity", 'g', pid);
+ printf("pid %d's %s affinity mask: "TASKSET_PRINTF_MASK"\n",
+ pid, current_new, from_cpuset(&mask));
+ if (!*argv) {
+ /* 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;
+ }
+ *argv = NULL;
+ current_new += 8; /* "new" */
+ }
+
+ { /* Affinity was specified, translate it into cpu_set_t */
+ unsigned i;
+ /* Do not allow zero mask: */
+ unsigned long long m = xstrtoull_range(aff, 0, 1, ULLONG_MAX);
+ enum { CNT_BIT = CPU_SETSIZE < sizeof(m)*8 ? CPU_SETSIZE : sizeof(m)*8 };
+
+ CPU_ZERO(&mask);
+ for (i = 0; i < CNT_BIT; i++) {
+ unsigned long long bit = (1ULL << i);
+ if (bit & m)
+ CPU_SET(i, &mask);
+ }
+ }
+
+ /* Set pid's or our own (pid==0) affinity */
+ if (sched_setaffinity(pid, sizeof(mask), &mask))
+ bb_perror_msg_and_die("can't %cet pid %d's affinity", 's', pid);
+
+ if (!*argv) /* "-p <aff> <pid> [...ignored...]" */
+ goto print_aff; /* print new affinity and exit */
+
+ BB_EXECVP(*argv, argv);
+ bb_simple_perror_msg_and_die(*argv);
+}
diff --git a/miscutils/time.c b/miscutils/time.c
new file mode 100644
index 0000000..dbc92d1
--- /dev/null
+++ b/miscutils/time.c
@@ -0,0 +1,429 @@
+/* vi: set sw=4 ts=4: */
+/* 'time' utility to display resource usage of processes.
+ Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
+
+ Licensed under GPL version 2, see file LICENSE in this tarball for details.
+*/
+/* Originally written by David Keppel <pardo@cs.washington.edu>.
+ Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
+ Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
+*/
+
+#include "libbb.h"
+
+/* Information on the resources used by a child process. */
+typedef struct {
+ int waitstatus;
+ struct rusage ru;
+ unsigned elapsed_ms; /* Wallclock time of process. */
+} resource_t;
+
+/* msec = milliseconds = 1/1,000 (1*10e-3) second.
+ usec = microseconds = 1/1,000,000 (1*10e-6) second. */
+
+#define UL unsigned long
+
+static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
+
+/* The output format for the -p option .*/
+static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
+
+/* Format string for printing all statistics verbosely.
+ Keep this output to 24 lines so users on terminals can see it all.*/
+static const char long_format[] ALIGN1 =
+ "\tCommand being timed: \"%C\"\n"
+ "\tUser time (seconds): %U\n"
+ "\tSystem time (seconds): %S\n"
+ "\tPercent of CPU this job got: %P\n"
+ "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
+ "\tAverage shared text size (kbytes): %X\n"
+ "\tAverage unshared data size (kbytes): %D\n"
+ "\tAverage stack size (kbytes): %p\n"
+ "\tAverage total size (kbytes): %K\n"
+ "\tMaximum resident set size (kbytes): %M\n"
+ "\tAverage resident set size (kbytes): %t\n"
+ "\tMajor (requiring I/O) page faults: %F\n"
+ "\tMinor (reclaiming a frame) page faults: %R\n"
+ "\tVoluntary context switches: %w\n"
+ "\tInvoluntary context switches: %c\n"
+ "\tSwaps: %W\n"
+ "\tFile system inputs: %I\n"
+ "\tFile system outputs: %O\n"
+ "\tSocket messages sent: %s\n"
+ "\tSocket messages received: %r\n"
+ "\tSignals delivered: %k\n"
+ "\tPage size (bytes): %Z\n"
+ "\tExit status: %x";
+
+/* Wait for and fill in data on child process PID.
+ Return 0 on error, 1 if ok. */
+/* pid_t is short on BSDI, so don't try to promote it. */
+static void resuse_end(pid_t pid, resource_t *resp)
+{
+ pid_t caught;
+
+ /* Ignore signals, but don't ignore the children. When wait3
+ returns the child process, set the time the command finished. */
+ while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
+ if (caught == -1 && errno != EINTR) {
+ bb_perror_msg("wait");
+ return;
+ }
+ }
+ resp->elapsed_ms = (monotonic_us() / 1000) - resp->elapsed_ms;
+}
+
+static void printargv(char *const *argv)
+{
+ const char *fmt = " %s" + 1;
+ do {
+ printf(fmt, *argv);
+ fmt = " %s";
+ } while (*++argv);
+}
+
+/* Return the number of kilobytes corresponding to a number of pages PAGES.
+ (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
+
+ Try to do arithmetic so that the risk of overflow errors is minimized.
+ This is funky since the pagesize could be less than 1K.
+ Note: Some machines express getrusage statistics in terms of K,
+ others in terms of pages. */
+static unsigned long ptok(unsigned pagesize, unsigned long pages)
+{
+ unsigned long tmp;
+
+ /* Conversion. */
+ if (pages > (LONG_MAX / pagesize)) { /* Could overflow. */
+ tmp = pages / 1024; /* Smaller first, */
+ return tmp * pagesize; /* then larger. */
+ }
+ /* Could underflow. */
+ tmp = pages * pagesize; /* Larger first, */
+ return tmp / 1024; /* then smaller. */
+}
+
+/* summarize: Report on the system use of a command.
+
+ Print the FMT argument except that `%' sequences
+ have special meaning, and `\n' and `\t' are translated into
+ newline and tab, respectively, and `\\' is translated into `\'.
+
+ The character following a `%' can be:
+ (* means the tcsh time builtin also recognizes it)
+ % == a literal `%'
+ C == command name and arguments
+* D == average unshared data size in K (ru_idrss+ru_isrss)
+* E == elapsed real (wall clock) time in [hour:]min:sec
+* F == major page faults (required physical I/O) (ru_majflt)
+* I == file system inputs (ru_inblock)
+* K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
+* M == maximum resident set size in K (ru_maxrss)
+* O == file system outputs (ru_oublock)
+* P == percent of CPU this job got (total cpu time / elapsed time)
+* R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
+* S == system (kernel) time (seconds) (ru_stime)
+* T == system time in [hour:]min:sec
+* U == user time (seconds) (ru_utime)
+* u == user time in [hour:]min:sec
+* W == times swapped out (ru_nswap)
+* X == average amount of shared text in K (ru_ixrss)
+ Z == page size
+* c == involuntary context switches (ru_nivcsw)
+ e == elapsed real time in seconds
+* k == signals delivered (ru_nsignals)
+ p == average unshared stack size in K (ru_isrss)
+* r == socket messages received (ru_msgrcv)
+* s == socket messages sent (ru_msgsnd)
+ t == average resident set size in K (ru_idrss)
+* w == voluntary context switches (ru_nvcsw)
+ x == exit status of command
+
+ Various memory usages are found by converting from page-seconds
+ to kbytes by multiplying by the page size, dividing by 1024,
+ and dividing by elapsed real time.
+
+ FMT is the format string, interpreted as described above.
+ COMMAND is the command and args that are being summarized.
+ RESP is resource information on the command. */
+
+#ifndef TICKS_PER_SEC
+#define TICKS_PER_SEC 100
+#endif
+
+static void summarize(const char *fmt, char **command, resource_t *resp)
+{
+ unsigned vv_ms; /* Elapsed virtual (CPU) milliseconds */
+ unsigned cpu_ticks; /* Same, in "CPU ticks" */
+ unsigned pagesize = getpagesize();
+
+ /* Impossible: we do not use WUNTRACED flag in wait()...
+ if (WIFSTOPPED(resp->waitstatus))
+ printf("Command stopped by signal %u\n",
+ WSTOPSIG(resp->waitstatus));
+ else */
+ if (WIFSIGNALED(resp->waitstatus))
+ printf("Command terminated by signal %u\n",
+ WTERMSIG(resp->waitstatus));
+ else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
+ printf("Command exited with non-zero status %u\n",
+ WEXITSTATUS(resp->waitstatus));
+
+ vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
+ + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
+
+#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
+ /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */
+ cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
+#else
+ cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
+#endif
+ if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */
+
+ while (*fmt) {
+ /* Handle leading literal part */
+ int n = strcspn(fmt, "%\\");
+ if (n) {
+ printf("%.*s", n, fmt);
+ fmt += n;
+ continue;
+ }
+
+ switch (*fmt) {
+#ifdef NOT_NEEDED
+ /* Handle literal char */
+ /* Usually we optimize for size, but there is a limit
+ * for everything. With this we do a lot of 1-byte writes */
+ default:
+ bb_putchar(*fmt);
+ break;
+#endif
+
+ case '%':
+ switch (*++fmt) {
+#ifdef NOT_NEEDED_YET
+ /* Our format strings do not have these */
+ /* and we do not take format str from user */
+ default:
+ bb_putchar('%');
+ /*FALLTHROUGH*/
+ case '%':
+ if (!*fmt) goto ret;
+ bb_putchar(*fmt);
+ break;
+#endif
+ case 'C': /* The command that got timed. */
+ printargv(command);
+ break;
+ case 'D': /* Average unshared data size. */
+ printf("%lu",
+ (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+ ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
+ break;
+ case 'E': { /* Elapsed real (wall clock) time. */
+ unsigned seconds = resp->elapsed_ms / 1000;
+ if (seconds >= 3600) /* One hour -> h:m:s. */
+ printf("%uh %um %02us",
+ seconds / 3600,
+ (seconds % 3600) / 60,
+ seconds % 60);
+ else
+ printf("%um %u.%02us", /* -> m:s. */
+ seconds / 60,
+ seconds % 60,
+ (unsigned)(resp->elapsed_ms / 10) % 100);
+ break;
+ }
+ case 'F': /* Major page faults. */
+ printf("%lu", resp->ru.ru_majflt);
+ break;
+ case 'I': /* Inputs. */
+ printf("%lu", resp->ru.ru_inblock);
+ break;
+ case 'K': /* Average mem usage == data+stack+text. */
+ printf("%lu",
+ (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+ ptok(pagesize, (UL) resp->ru.ru_isrss) +
+ ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
+ break;
+ case 'M': /* Maximum resident set size. */
+ printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
+ break;
+ case 'O': /* Outputs. */
+ printf("%lu", resp->ru.ru_oublock);
+ break;
+ case 'P': /* Percent of CPU this job got. */
+ /* % cpu is (total cpu time)/(elapsed time). */
+ if (resp->elapsed_ms > 0)
+ printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
+ else
+ printf("?%%");
+ break;
+ case 'R': /* Minor page faults (reclaims). */
+ printf("%lu", resp->ru.ru_minflt);
+ break;
+ case 'S': /* System time. */
+ printf("%u.%02u",
+ (unsigned)resp->ru.ru_stime.tv_sec,
+ (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+ break;
+ case 'T': /* System time. */
+ if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
+ printf("%uh %um %02us",
+ (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
+ (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
+ (unsigned)(resp->ru.ru_stime.tv_sec % 60));
+ else
+ printf("%um %u.%02us", /* -> m:s. */
+ (unsigned)(resp->ru.ru_stime.tv_sec / 60),
+ (unsigned)(resp->ru.ru_stime.tv_sec % 60),
+ (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+ break;
+ case 'U': /* User time. */
+ printf("%u.%02u",
+ (unsigned)resp->ru.ru_utime.tv_sec,
+ (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+ break;
+ case 'u': /* User time. */
+ if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
+ printf("%uh %um %02us",
+ (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
+ (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
+ (unsigned)(resp->ru.ru_utime.tv_sec % 60));
+ else
+ printf("%um %u.%02us", /* -> m:s. */
+ (unsigned)(resp->ru.ru_utime.tv_sec / 60),
+ (unsigned)(resp->ru.ru_utime.tv_sec % 60),
+ (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+ break;
+ case 'W': /* Times swapped out. */
+ printf("%lu", resp->ru.ru_nswap);
+ break;
+ case 'X': /* Average shared text size. */
+ printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
+ break;
+ case 'Z': /* Page size. */
+ printf("%u", getpagesize());
+ break;
+ case 'c': /* Involuntary context switches. */
+ printf("%lu", resp->ru.ru_nivcsw);
+ break;
+ case 'e': /* Elapsed real time in seconds. */
+ printf("%u.%02u",
+ (unsigned)resp->elapsed_ms / 1000,
+ (unsigned)(resp->elapsed_ms / 10) % 100);
+ break;
+ case 'k': /* Signals delivered. */
+ printf("%lu", resp->ru.ru_nsignals);
+ break;
+ case 'p': /* Average stack segment. */
+ printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
+ break;
+ case 'r': /* Incoming socket messages received. */
+ printf("%lu", resp->ru.ru_msgrcv);
+ break;
+ case 's': /* Outgoing socket messages sent. */
+ printf("%lu", resp->ru.ru_msgsnd);
+ break;
+ case 't': /* Average resident set size. */
+ printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
+ break;
+ case 'w': /* Voluntary context switches. */
+ printf("%lu", resp->ru.ru_nvcsw);
+ break;
+ case 'x': /* Exit status. */
+ printf("%u", WEXITSTATUS(resp->waitstatus));
+ break;
+ }
+ break;
+
+#ifdef NOT_NEEDED_YET
+ case '\\': /* Format escape. */
+ switch (*++fmt) {
+ default:
+ bb_putchar('\\');
+ /*FALLTHROUGH*/
+ case '\\':
+ if (!*fmt) goto ret;
+ bb_putchar(*fmt);
+ break;
+ case 't':
+ bb_putchar('\t');
+ break;
+ case 'n':
+ bb_putchar('\n');
+ break;
+ }
+ break;
+#endif
+ }
+ ++fmt;
+ }
+ /* ret: */
+ bb_putchar('\n');
+}
+
+/* Run command CMD and return statistics on it.
+ Put the statistics in *RESP. */
+static void run_command(char *const *cmd, resource_t *resp)
+{
+ pid_t pid; /* Pid of child. */
+ void (*interrupt_signal)(int);
+ void (*quit_signal)(int);
+
+ resp->elapsed_ms = monotonic_us() / 1000;
+ pid = vfork(); /* Run CMD as child process. */
+ if (pid < 0)
+ bb_perror_msg_and_die("fork");
+ if (pid == 0) { /* If child. */
+ /* Don't cast execvp arguments; that causes errors on some systems,
+ versus merely warnings if the cast is left off. */
+ BB_EXECVP(cmd[0], cmd);
+ xfunc_error_retval = (errno == ENOENT ? 127 : 126);
+ bb_error_msg_and_die("cannot run %s", cmd[0]);
+ }
+
+ /* Have signals kill the child but not self (if possible). */
+//TODO: just block all sigs? and reenable them in the very end in main?
+ interrupt_signal = signal(SIGINT, SIG_IGN);
+ quit_signal = signal(SIGQUIT, SIG_IGN);
+
+ resuse_end(pid, resp);
+
+ /* Re-enable signals. */
+ signal(SIGINT, interrupt_signal);
+ signal(SIGQUIT, quit_signal);
+}
+
+int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int time_main(int argc UNUSED_PARAM, char **argv)
+{
+ resource_t res;
+ const char *output_format = default_format;
+ int opt;
+
+ opt_complementary = "-1"; /* at least one arg */
+ /* "+": stop on first non-option */
+ opt = getopt32(argv, "+vp");
+ argv += optind;
+ if (opt & 1)
+ output_format = long_format;
+ if (opt & 2)
+ output_format = posix_format;
+
+ run_command(argv, &res);
+
+ /* Cheat. printf's are shorter :) */
+ /* (but see bb_putchar() body for additional wrinkle!) */
+ xdup2(2, 1); /* just in case libc does something silly :( */
+ stdout = stderr;
+ summarize(output_format, argv, &res);
+
+ if (WIFSTOPPED(res.waitstatus))
+ return WSTOPSIG(res.waitstatus);
+ if (WIFSIGNALED(res.waitstatus))
+ return WTERMSIG(res.waitstatus);
+ if (WIFEXITED(res.waitstatus))
+ return WEXITSTATUS(res.waitstatus);
+ fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/ttysize.c b/miscutils/ttysize.c
new file mode 100644
index 0000000..0545554
--- /dev/null
+++ b/miscutils/ttysize.c
@@ -0,0 +1,44 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Replacement for "stty size", which is awkward for shell script use.
+ * - Allows to request width, height, or both, in any order.
+ * - Does not complain on error, but returns width 80, height 24.
+ * - Size: less than 200 bytes
+ *
+ * Copyright (C) 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under the GPL v2, see the file LICENSE in this tarball.
+ */
+#include "libbb.h"
+
+int ttysize_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ttysize_main(int argc, char **argv)
+{
+ unsigned w, h;
+ struct winsize wsz;
+
+ w = 80;
+ h = 24;
+ if (!ioctl(0, TIOCGWINSZ, &wsz)) {
+ w = wsz.ws_col;
+ h = wsz.ws_row;
+ }
+
+ if (argc == 1) {
+ printf("%u %u", w, h);
+ } else {
+ const char *fmt, *arg;
+
+ fmt = "%u %u" + 3; /* "%u" */
+ while ((arg = *++argv) != NULL) {
+ char c = arg[0];
+ if (c == 'w')
+ printf(fmt, w);
+ if (c == 'h')
+ printf(fmt, h);
+ fmt = "%u %u" + 2; /* " %u" */
+ }
+ }
+ bb_putchar('\n');
+ return 0;
+}
diff --git a/miscutils/watchdog.c b/miscutils/watchdog.c
new file mode 100644
index 0000000..75a399f
--- /dev/null
+++ b/miscutils/watchdog.c
@@ -0,0 +1,80 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini watchdog implementation for busybox
+ *
+ * Copyright (C) 2003 Paul Mundt <lethal@linux-sh.org>
+ * Copyright (C) 2006 Bernhard Reutner-Fischer <busybox@busybox.net>
+ * Copyright (C) 2008 Darius Augulis <augulis.darius@gmail.com>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include "linux/watchdog.h"
+
+#define OPT_FOREGROUND (1 << 0)
+#define OPT_STIMER (1 << 1)
+#define OPT_HTIMER (1 << 2)
+
+static void watchdog_shutdown(int sig UNUSED_PARAM)
+{
+ static const char V = 'V';
+
+ write(3, &V, 1); /* Magic, see watchdog-api.txt in kernel */
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(3);
+ exit(EXIT_SUCCESS);
+}
+
+int watchdog_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int watchdog_main(int argc, char **argv)
+{
+ static const struct suffix_mult suffixes[] = {
+ { "ms", 1 },
+ { "", 1000 },
+ { }
+ };
+
+ unsigned opts;
+ unsigned stimer_duration; /* how often to restart */
+ unsigned htimer_duration = 60000; /* reboots after N ms if not restarted */
+ char *st_arg;
+ char *ht_arg;
+
+ opt_complementary = "=1"; /* must have exactly 1 argument */
+ opts = getopt32(argv, "Ft:T:", &st_arg, &ht_arg);
+
+ if (opts & OPT_HTIMER)
+ htimer_duration = xatou_sfx(ht_arg, suffixes);
+ stimer_duration = htimer_duration / 2;
+ if (opts & OPT_STIMER)
+ stimer_duration = xatou_sfx(st_arg, suffixes);
+
+ bb_signals(BB_FATAL_SIGS, watchdog_shutdown);
+
+ /* Use known fd # - avoid needing global 'int fd' */
+ xmove_fd(xopen(argv[argc - 1], O_WRONLY), 3);
+
+ /* WDIOC_SETTIMEOUT takes seconds, not milliseconds */
+ htimer_duration = htimer_duration / 1000;
+ ioctl_or_warn(3, WDIOC_SETTIMEOUT, &htimer_duration);
+#if 0
+ ioctl_or_warn(3, WDIOC_GETTIMEOUT, &htimer_duration);
+ printf("watchdog: SW timer is %dms, HW timer is %dms\n",
+ stimer_duration, htimer_duration * 1000);
+#endif
+
+ if (!(opts & OPT_FOREGROUND)) {
+ bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+ }
+
+ while (1) {
+ /*
+ * Make sure we clear the counter before sleeping, as the counter value
+ * is undefined at this point -- PFM
+ */
+ write(3, "", 1); /* write zero byte */
+ usleep(stimer_duration * 1000L);
+ }
+ return EXIT_SUCCESS; /* - not reached, but gcc 4.2.1 is too dumb! */
+}