From 73b16af8feec390afbabd9356d6e5e83c0390838 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:20:47 +0200 Subject: busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- miscutils/Config.in | 552 +++++++++++++ miscutils/Kbuild | 38 + miscutils/adjtimex.c | 145 ++++ miscutils/bbconfig.c | 12 + miscutils/chat.c | 444 ++++++++++ miscutils/chrt.c | 123 +++ miscutils/crond.c | 935 +++++++++++++++++++++ miscutils/crontab.c | 235 ++++++ miscutils/dc.c | 256 ++++++ miscutils/devfsd.c | 1801 +++++++++++++++++++++++++++++++++++++++++ miscutils/devmem.c | 128 +++ miscutils/eject.c | 116 +++ miscutils/fbsplash.c | 393 +++++++++ miscutils/fbsplash.cfg | 9 + miscutils/hdparm.c | 2063 +++++++++++++++++++++++++++++++++++++++++++++++ miscutils/inotifyd.c | 152 ++++ miscutils/last.c | 133 +++ miscutils/last_fancy.c | 297 +++++++ miscutils/less.c | 1801 +++++++++++++++++++++++++++++++++++++++++ miscutils/makedevs.c | 209 +++++ miscutils/man.c | 269 ++++++ miscutils/microcom.c | 171 ++++ miscutils/mountpoint.c | 66 ++ miscutils/mt.c | 140 ++++ miscutils/raidautorun.c | 25 + miscutils/readahead.c | 40 + miscutils/runlevel.c | 43 + miscutils/rx.c | 254 ++++++ miscutils/setsid.c | 35 + miscutils/strings.c | 84 ++ miscutils/taskset.c | 137 ++++ miscutils/time.c | 429 ++++++++++ miscutils/ttysize.c | 44 + miscutils/watchdog.c | 80 ++ 34 files changed, 11659 insertions(+) create mode 100644 miscutils/Config.in create mode 100644 miscutils/Kbuild create mode 100644 miscutils/adjtimex.c create mode 100644 miscutils/bbconfig.c create mode 100644 miscutils/chat.c create mode 100644 miscutils/chrt.c create mode 100644 miscutils/crond.c create mode 100644 miscutils/crontab.c create mode 100644 miscutils/dc.c create mode 100644 miscutils/devfsd.c create mode 100644 miscutils/devmem.c create mode 100644 miscutils/eject.c create mode 100644 miscutils/fbsplash.c create mode 100644 miscutils/fbsplash.cfg create mode 100644 miscutils/hdparm.c create mode 100644 miscutils/inotifyd.c create mode 100644 miscutils/last.c create mode 100644 miscutils/last_fancy.c create mode 100644 miscutils/less.c create mode 100644 miscutils/makedevs.c create mode 100644 miscutils/man.c create mode 100644 miscutils/microcom.c create mode 100644 miscutils/mountpoint.c create mode 100644 miscutils/mt.c create mode 100644 miscutils/raidautorun.c create mode 100644 miscutils/readahead.c create mode 100644 miscutils/runlevel.c create mode 100644 miscutils/rx.c create mode 100644 miscutils/setsid.c create mode 100644 miscutils/strings.c create mode 100644 miscutils/taskset.c create mode 100644 miscutils/time.c create mode 100644 miscutils/ttysize.c create mode 100644 miscutils/watchdog.c (limited to 'miscutils') 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/ 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" = 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 +# +# 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 + * + * busyboxed 20 March 2001, Larry Doolittle + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "libbb.h" +#include + +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 + * + * 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 +#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_str; + pid_str = *argv; + } + /* else "-p ", 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 ", + * or it was "-p " 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 [...]" */ + 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 -f -b + * + * run as root, but NOT setuid root + * + * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) + * (version 2.3.2) + * Vladimir Oleynik (C) 2002 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include + +/* 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 (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 + +/* 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 + + 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 + +#include +#include + +/* 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. + The path to read the database from. If this is a directory, all + entries in that directory will be read(except hidden entries). + If TRUE, the routine will silently ignore a missing config file. + 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. + The configuration line. + 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. + The open control file. + 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. + 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. + The devfs change. + 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. + The devfs change. + 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. + The devfs change. + The config file entry. + The number of subexpression(start, end) offsets within the + device name. + The number of elements within <>. + [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. + The devfs change. + The config file entry. + This list of subexpression(start, end) offsets within the + device name. + The number of elements in <>. + [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. + The devfs change. + 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. + The destination path. An existing inode may be deleted. + The destination stat(2) information. + The desired new mode for the destination. + The source path. + 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. + "UID" or "GID". + 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. + 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. + To choose which function to perform + The directory pointer. This is closed upon completion. + The name of the directory. + 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. + The string contained in the symlink. + 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. + 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. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may equal <>. + 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. + An arbitrary pointer passed to <>. + Device name; specifically, this is the string that contains all + of the regular subexpressions. + Array of start / end offsets into info->devname for each subexpression + Number of regular subexpressions found in <>. + [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. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may NOT equal <>, 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. + Device name; specifically, this is the string that contains all + of the regular subexpressions. + An array of start and end offsets into <>, one for each + subexpression + Number of subexpressions in the offset-array <>. + [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. + The device name provided by the kernel. + The length of the name. + A buffer that may be used. This should be at least 128 bytes long. + The major number for the device. + 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. + The major number for the device. + 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. + The buffer to write to. + The major number for the device. + The minor number for the device. + 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. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may equal <>. + 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. + An arbitrary pointer passed to <>. + [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. + The buffer to write to. + The length of the output buffer. + The current output position. This is updated. + A pointer to the input character pointer. + 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. + An arbitrary pointer passed to <>. + 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 . + The variable name. + 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 + * Copyright (C) 2005 Tito Ragusa + * + * 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 +#include + +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 " 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 , + * + * + * 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" + +/* 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 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 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 + +/* 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<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< 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 + * + * 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 + +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 + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include + +/* 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 + * + * Licensed under the GPLv2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include + +/* 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 + * + * 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_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] */ + /* 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 * Find close bracket . + ESC-^B * Find open bracket + --------------------------------------------------- + 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 Mark the current position with . + ' 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 + - Toggle a command line option [see OPTIONS below]. + -- Toggle a command line option, by name. + _ Display the setting of a command line option. + __ 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 + * + * 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(""); + } + + 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 + * 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" + */ + *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 + * + * 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 + +/* 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 +#include + +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 + * + * 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 +#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 + * 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 + * In the public domain. + * + * 1999-02-22 Arkadiusz Mickiewicz + * - added Native Language Support + * + * 2001-01-18 John Fremlin + * - 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 + * + * 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 +#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 ...rest.is.ignored..." */ + aff = pid_str; + pid_str = *argv; /* NB: *argv != NULL in this case */ + } + /* else it was just "-p ", and *argv == NULL */ + pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1); + } else { + aff = *argv++; /* */ + 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 ", + * or it was "-p " 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 [...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 . + Heavily modified by David MacKenzie . + Heavily modified for busybox by Erik Andersen +*/ + +#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 + * + * 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 + * Copyright (C) 2006 Bernhard Reutner-Fischer + * Copyright (C) 2008 Darius Augulis + * + * 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! */ +} -- cgit v1.2.3